/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <vcl/svapp.hxx>
#include <vcl/window.hxx>
#include <editeng/lspcitem.hxx>
#include <editeng/flditem.hxx>
#include "impedit.hxx"
#include <editeng/editeng.hxx>
#include <editeng/editview.hxx>
#include <eerdll2.hxx>
#include <editeng/eerdll.hxx>
#include <edtspell.hxx>
#include "eeobj.hxx"
#include <editeng/txtrange.hxx>
#include <sfx2/app.hxx>
#include <sfx2/mieclip.hxx>
#include <sfx2/viewsh.hxx>
#include <svtools/colorcfg.hxx>
#include <svl/ctloptions.hxx>
#include <unotools/securityoptions.hxx>
#include <editeng/acorrcfg.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/adjustitem.hxx>
#include <editeng/frmdiritem.hxx>
#include <editeng/justifyitem.hxx>
#include <editeng/udlnitem.hxx>
 
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
#include <com/sun/star/system/SystemShellExecute.hpp>
#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
#include <com/sun/star/system/XSystemShellExecute.hpp>
#include <com/sun/star/i18n/UnicodeType.hpp>
 
#include <rtl/character.hxx>
 
#include <sal/log.hxx>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <sot/exchange.hxx>
#include <sot/formats.hxx>
#include <svl/asiancfg.hxx>
#include <svl/voiditem.hxx>
#include <i18nutil/unicode.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/flagguard.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/configuration.hxx>
 
#include <unicode/ubidi.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <string_view>
#include <fstream>
 
using namespace ::com::sun::star;
 
static sal_uInt16 lcl_CalcExtraSpace( const SvxLineSpacingItem& rLSItem )
{
    sal_uInt16 nExtra = 0;
    if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
    {
        nExtra = rLSItem.GetInterLineSpace();
    }
 
    return nExtra;
}
 
constexpr tools::Long constMaxPaperSize = 0x7FFFFFFF;
 
ImpEditEngine::ImpEditEngine( EditEngine* pEE, SfxItemPool* pItemPool ) :
    pSharedVCL(EditDLL::Get().GetSharedVclResources()),
    maPaperSize(constMaxPaperSize, constMaxPaperSize),
    maMinAutoPaperSize(0, 0),
    maMaxAutoPaperSize(constMaxPaperSize, constMaxPaperSize),
    maEditDoc( pItemPool ),
    mpEditEngine(pEE),
    mpActiveView(nullptr),
    mpStylePool(nullptr),
    mpTextObjectPool(nullptr),
    mpUndoManager(nullptr),
    maWordDelimiters(u" .,;:-`'?!_=\"{}()[]"_ustr),
    maBackgroundColor(COL_AUTO),
    mbRoundToNearestPt(false),
    mnAsianCompressionMode(CharCompressType::NONE),
    meDefaultHorizontalTextDirection(EEHorizontalTextDirection::Default),
    mnBigTextObjectStart(20),
    meDefLanguage(LANGUAGE_DONTKNOW),
    mnCurTextHeight(0),
    maOnlineSpellTimer("editeng::ImpEditEngine aOnlineSpellTimer"),
    maStatusTimer("editeng::ImpEditEngine aStatusTimer"),
    mbKernAsianPunctuation(false),
    mbAddExtLeading(false),
    mbIsFormatting(false),
    mbFormatted(false),
    mbInSelection(false),
    mbIsInUndo(false),
    mbUpdateLayout(true),
    mbUndoEnabled(true),
    mbDowning(false),
    mbUseAutoColor(true),
    mbForceAutoColor(false),
    mbCallParaInsertedOrDeleted(false),
    mbFirstWordCapitalization(true),
    mbLastTryMerge(false),
    mbReplaceLeadingSingleQuotationMark(true),
    mbSkipOutsideFormat(false),
    mbFuzzing(comphelper::IsFuzzing()),
    mbNbspRunNext(false)
{
    maStatus.GetControlWord() =  EEControlBits::USECHARATTRIBS | EEControlBits::DOIDLEFORMAT |
                                EEControlBits::PASTESPECIAL | EEControlBits::UNDOATTRIBS |
                                EEControlBits::ALLOWBIGOBJS | EEControlBits::RTFSTYLESHEETS;
 
    maSelEngine.SetFunctionSet(&maSelFuncSet);
 
    maStatusTimer.SetTimeout(200);
    maStatusTimer.SetInvokeHandler(LINK(this, ImpEditEngine, StatusTimerHdl));
 
    maIdleFormatter.SetPriority(TaskPriority::REPAINT);
    maIdleFormatter.SetInvokeHandler(LINK(this, ImpEditEngine, IdleFormatHdl));
 
    maOnlineSpellTimer.SetTimeout(100);
    maOnlineSpellTimer.SetInvokeHandler(LINK( this, ImpEditEngine, OnlineSpellHdl));
 
    // Access data already from here on!
    SetRefDevice( nullptr );
    InitDoc( false );
 
    mbCallParaInsertedOrDeleted = true;
 
    maEditDoc.SetModifyHdl( LINK( this, ImpEditEngine, DocModified ) );
    StartListening(*SfxGetpApp());
}
 
void ImpEditEngine::Dispose()
{
    SolarMutexGuard g;
    auto pApp = SfxApplication::Get();
    if(pApp)
        EndListening(*pApp);
    mpVirtDev.disposeAndClear();
    mpOwnDev.disposeAndClear();
    pSharedVCL.reset();
}
 
ImpEditEngine::~ImpEditEngine()
{
    maStatusTimer.Stop();
    maOnlineSpellTimer.Stop();
    maIdleFormatter.Stop();
 
    // Destroying templates may otherwise cause unnecessary formatting,
    // when a parent template is destroyed.
    // And this after the destruction of the data!
    mbDowning = true;
    SetUpdateLayout( false );
 
    Dispose();
    // it's only legal to delete the mpUndoManager if it was created by
    // ImpEditEngine; if it was set by SetUndoManager() it must be cleared
    // before destroying the ImpEditEngine!
    assert(!mpUndoManager || typeid(*mpUndoManager) == typeid(EditUndoManager));
    delete mpUndoManager;
    mpTextRanger.reset();
    mpIMEInfos.reset();
    mpSpellInfo.reset();
}
 
void ImpEditEngine::SetRefDevice(OutputDevice* pRef)
{
    if (pRef)
        mpRefDev = pRef;
    else
        mpRefDev = pSharedVCL->GetVirtualDevice();
 
    mnOnePixelInRef = static_cast<sal_uInt16>(mpRefDev->PixelToLogic( Size( 1, 0 ) ).Width());
 
    if ( IsFormatted() )
    {
        FormatFullDoc();
        UpdateViews();
    }
}
 
void ImpEditEngine::SetRefMapMode( const MapMode& rMapMode )
{
    if ( GetRefDevice()->GetMapMode() == rMapMode )
        return;
 
    mpOwnDev.disposeAndClear();
    mpOwnDev = VclPtr<VirtualDevice>::Create();
    mpRefDev = mpOwnDev;
    mpRefDev->SetMapMode(MapMode(MapUnit::MapTwip));
    SetRefDevice(mpRefDev);
 
    mpRefDev->SetMapMode( rMapMode );
    mnOnePixelInRef = static_cast<sal_uInt16>(mpRefDev->PixelToLogic(Size(1, 0)).Width());
    if ( IsFormatted() )
    {
        FormatFullDoc();
        UpdateViews();
    }
}
 
void ImpEditEngine::InitDoc(bool bKeepParaAttribs)
{
    sal_Int32 nParas = maEditDoc.Count();
    for ( sal_Int32 n = bKeepParaAttribs ? 1 : 0; n < nParas; n++ )
    {
        if (maEditDoc.GetObject(n)->GetStyleSheet())
            EndListening( *maEditDoc.GetObject(n)->GetStyleSheet() );
    }
 
    if ( bKeepParaAttribs )
        maEditDoc.RemoveText();
    else
        maEditDoc.Clear();
 
    GetParaPortions().Reset();
 
    GetParaPortions().Insert(0, std::make_unique<ParaPortion>(maEditDoc.GetObject(0)));
 
    mbFormatted = false;
 
    if ( IsCallParaInsertedOrDeleted() )
    {
        GetEditEnginePtr()->ParagraphDeleted( EE_PARA_ALL );
        GetEditEnginePtr()->ParagraphInserted( 0 );
    }
 
    if ( GetStatus().DoOnlineSpelling() )
        maEditDoc.GetObject( 0 )->CreateWrongList();
}
 
EditPaM ImpEditEngine::DeleteSelected(const EditSelection& rSel)
{
    EditPaM aPaM (ImpDeleteSelection(rSel));
    return aPaM;
}
 
OUString ImpEditEngine::GetText( const ESelection& rESelection )
{
    EditSelection aSel = CreateSel(rESelection);
    return GetSelected(aSel);
}
 
OUString ImpEditEngine::GetSelected( const EditSelection& rSel  ) const
{
    if ( !rSel.HasRange() )
        return OUString();
 
    EditSelection aSel( rSel );
    aSel.Adjust( maEditDoc );
 
    ContentNode* pStartNode = aSel.Min().GetNode();
    ContentNode* pEndNode = aSel.Max().GetNode();
    sal_Int32 nStartNode = maEditDoc.GetPos( pStartNode );
    sal_Int32 nEndNode = maEditDoc.GetPos( pEndNode );
 
    OSL_ENSURE( nStartNode <= nEndNode, "Selection not sorted ?" );
 
    // calculate buffer size we need
    sal_Int32 nBufSize = (aSel.Max().GetIndex() - aSel.Min().GetIndex() + 1)
                         + (nEndNode - nStartNode + 1);
    // protect against sometimes whacky selection
    if (nBufSize < 0 || nBufSize > 64 * 1024)
        nBufSize = 256;
    OUStringBuffer aText(nBufSize);
    const OUString aSep = EditDoc::GetSepStr( LINEEND_LF );
 
    // iterate over the paragraphs ...
    for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ )
    {
        const ContentNode* pNode = maEditDoc.GetObject( nNode );
        assert(pNode);
 
        const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0;
        const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart!
 
        aText.append(EditDoc::GetParaAsString( pNode, nStartPos, nEndPos ));
        if ( nNode < nEndNode )
            aText.append(aSep);
    }
    return aText.makeStringAndClear();
}
 
bool ImpEditEngine::MouseButtonDown( const MouseEvent& rMEvt, EditView* pView )
{
    GetSelEngine().SetCurView( pView );
    SetActiveView( pView );
 
    if (!GetAutoCompleteText().isEmpty())
        SetAutoCompleteText( OUString(), true );
 
    GetSelEngine().SelMouseButtonDown( rMEvt );
    // Special treatment
    EditSelection aCurSel( pView->getImpl().GetEditSelection() );
    if ( rMEvt.IsShift() )
        return true;
 
    if ( rMEvt.GetClicks() == 2 )
    {
        // So that the SelectionEngine knows about the anchor.
        maSelEngine.CursorPosChanging( true, false );
 
        EditSelection aNewSelection( SelectWord( aCurSel ) );
        pView->getImpl().DrawSelectionXOR();
        pView->getImpl().SetEditSelection( aNewSelection );
        pView->getImpl().DrawSelectionXOR();
        pView->ShowCursor();
    }
    else if ( rMEvt.GetClicks() == 3 )
    {
        // So that the SelectionEngine knows about the anchor.
        maSelEngine.CursorPosChanging( true, false );
 
        EditSelection aNewSelection( aCurSel );
        aNewSelection.Min().SetIndex( 0 );
        aNewSelection.Max().SetIndex( aCurSel.Min().GetNode()->Len() );
        pView->getImpl().DrawSelectionXOR();
        pView->getImpl().SetEditSelection( aNewSelection );
        pView->getImpl().DrawSelectionXOR();
        pView->ShowCursor();
    }
    return true;
}
 
bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView )
{
    bool bConsumed = true;
 
    GetSelEngine().SetCurView( pView );
    SetActiveView( pView );
    if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
    {
        if (!pView->IsReadOnly())
        {
            pView->DeleteSelected();
            mpIMEInfos.reset();
            EditPaM aPaM = pView->getImpl().GetEditSelection().Max();
            OUString aOldTextAfterStartPos = aPaM.GetNode()->Copy( aPaM.GetIndex() );
            sal_Int32 nMax = aOldTextAfterStartPos.indexOf( CH_FEATURE );
            if ( nMax != -1 )  // don't overwrite features!
                aOldTextAfterStartPos = aOldTextAfterStartPos.copy( 0, nMax );
            mpIMEInfos.reset( new ImplIMEInfos( aPaM, aOldTextAfterStartPos ) );
            mpIMEInfos->bWasCursorOverwrite = !pView->IsInsertMode();
            UndoActionStart( EDITUNDO_INSERT );
        }
    }
    else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
    {
        if (!pView->IsReadOnly())
        {
            OSL_ENSURE( mpIMEInfos, "CommandEventId::EndExtTextInput => No start ?" );
            if( mpIMEInfos )
            {
                // #102812# convert quotes in IME text
                // works on the last input character, this is especially in Korean text often done
                // quotes that are inside of the string are not replaced!
                // Borrowed from sw: edtwin.cxx
                if ( mpIMEInfos->nLen )
                {
                    EditSelection aSel( mpIMEInfos->aPos );
                    aSel.Min().SetIndex( aSel.Min().GetIndex() + mpIMEInfos->nLen-1 );
                    aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen );
                    // #102812# convert quotes in IME text
                    // works on the last input character, this is especially in Korean text often done
                    // quotes that are inside of the string are not replaced!
                    // See also tdf#155350
                    const sal_Unicode nCharCode = aSel.Min().GetNode()->GetChar( aSel.Min().GetIndex() );
                    if ( ( GetStatus().DoAutoCorrect() ) && SvxAutoCorrect::IsAutoCorrectChar(nCharCode) )
                    {
                        aSel = DeleteSelected( aSel );
                        aSel = AutoCorrect( aSel, nCharCode, mpIMEInfos->bWasCursorOverwrite );
                        pView->getImpl().SetEditSelection( aSel );
                    }
                }
 
                ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() );
                if (pPortion)
                    pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() );
 
                bool bWasCursorOverwrite = mpIMEInfos->bWasCursorOverwrite;
 
                mpIMEInfos.reset();
 
                FormatAndLayout( pView );
 
                pView->SetInsertMode( !bWasCursorOverwrite );
            }
            UndoActionEnd();
        }
    }
    else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
    {
        if( mpIMEInfos && !pView->IsReadOnly())
        {
            OSL_ENSURE( mpIMEInfos, "CommandEventId::ExtTextInput => No Start ?" );
            const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
 
            if ( !pData->IsOnlyCursorChanged() )
            {
                EditSelection aSel( mpIMEInfos->aPos );
                aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen );
                aSel = DeleteSelected( aSel );
                aSel = ImpInsertText( aSel, pData->GetText() );
 
                if ( mpIMEInfos->bWasCursorOverwrite )
                {
                    sal_Int32 nOldIMETextLen = mpIMEInfos->nLen;
                    sal_Int32 nNewIMETextLen = pData->GetText().getLength();
 
                    if ( ( nOldIMETextLen > nNewIMETextLen ) &&
                         ( nNewIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
                    {
                        // restore old characters
                        sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
                        EditPaM aPaM( mpIMEInfos->aPos );
                        aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen );
                        ImpInsertText( EditSelection(aPaM), mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) );
                    }
                    else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
                              ( nOldIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
                    {
                        // overwrite
                        sal_Int32 nOverwrite = nNewIMETextLen - nOldIMETextLen;
                        if ( ( nOldIMETextLen + nOverwrite ) > mpIMEInfos->aOldTextAfterStartPos.getLength() )
                            nOverwrite = mpIMEInfos->aOldTextAfterStartPos.getLength() - nOldIMETextLen;
                        OSL_ENSURE( nOverwrite && (nOverwrite < 0xFF00), "IME Overwrite?!" );
                        EditPaM aPaM( mpIMEInfos->aPos );
                        aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen );
                        EditSelection _aSel( aPaM );
                        _aSel.Max().SetIndex( _aSel.Max().GetIndex() + nOverwrite );
                        DeleteSelected( _aSel );
                    }
                }
                if ( pData->GetTextAttr() )
                {
                    mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
                }
                else
                {
                    mpIMEInfos->DestroyAttribs();
                    mpIMEInfos->nLen = pData->GetText().getLength();
                }
 
                ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() );
                pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() );
                FormatAndLayout( pView );
            }
 
            EditSelection aNewSel( EditPaM( mpIMEInfos->aPos.GetNode(), mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() ) );
            pView->SetSelection( CreateESel( aNewSel ) );
            pView->SetInsertMode( !pData->IsCursorOverwrite() );
 
            if ( pData->IsCursorVisible() )
                pView->ShowCursor();
            else
                pView->HideCursor();
        }
    }
    else if ( rCEvt.GetCommand() == CommandEventId::InputContextChange )
    {
    }
    else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
    {
        EditPaM aPaM( pView->getImpl().GetEditSelection().Max() );
        tools::Rectangle aR1 = PaMtoEditCursor( aPaM );
 
        if ( !IsFormatted() )
            FormatDoc();
 
        ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( GetEditDoc().GetPos( aPaM.GetNode() ) );
        if (pParaPortion)
        {
            sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), true );
            const EditLine& rLine = pParaPortion->GetLines()[nLine];
 
            sal_Int32 nInputEnd;
            if (mpIMEInfos)
                nInputEnd = mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen;
            else
                nInputEnd = aPaM.GetIndex();
 
            if ( nInputEnd > rLine.GetEnd() )
                nInputEnd = rLine.GetEnd();
            tools::Rectangle aR2 = PaMtoEditCursor( EditPaM( aPaM.GetNode(), nInputEnd ), CursorFlags{ .bEndOfLine = true });
            tools::Rectangle aRect = pView->getImpl().GetWindowPos( aR1 );
            auto nExtTextInputWidth = aR2.Left() - aR1.Right();
            if (EditViewCallbacks* pEditViewCallbacks = pView->getEditViewCallbacks())
                pEditViewCallbacks->EditViewCursorRect(aRect, nExtTextInputWidth);
            else if (vcl::Window* pWindow = pView->GetWindow())
                pWindow->SetCursorRect(&aRect, nExtTextInputWidth);
        }
    }
    else if ( rCEvt.GetCommand() == CommandEventId::SelectionChange )
    {
        const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData();
 
        ESelection aSelection = pView->GetSelection();
        aSelection.Adjust();
 
        if( pView->HasSelection() )
        {
            aSelection.nEndPos = aSelection.nStartPos;
            aSelection.nStartPos += pData->GetStart();
            aSelection.nEndPos += pData->GetEnd();
        }
        else
        {
            aSelection.nStartPos = pData->GetStart();
            aSelection.nEndPos = pData->GetEnd();
        }
        pView->SetSelection( aSelection );
    }
    else if ( rCEvt.GetCommand() == CommandEventId::PrepareReconversion )
    {
        if ( pView->HasSelection() )
        {
            ESelection aSelection = pView->GetSelection();
            aSelection.Adjust();
 
            if ( aSelection.nStartPara != aSelection.nEndPara )
            {
                sal_Int32 aParaLen = mpEditEngine->GetTextLen( aSelection.nStartPara );
                aSelection.nEndPara = aSelection.nStartPara;
                aSelection.nEndPos = aParaLen;
                pView->SetSelection( aSelection );
            }
        }
    }
    else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition )
    {
        if (mpIMEInfos)
        {
            EditPaM aPaM( pView->getImpl().GetEditSelection().Max() );
            if ( !IsFormatted() )
                FormatDoc();
 
            sal_Int32 nPortionPos = GetEditDoc().GetPos(aPaM.GetNode());
            ParaPortion* pParaPortion = GetParaPortions().SafeGetObject(nPortionPos);
            if (pParaPortion)
            {
                const sal_Int32 nMinPos = mpIMEInfos->aPos.GetIndex();
                const sal_Int32 nMaxPos = nMinPos + mpIMEInfos->nLen - 1;
                std::vector<tools::Rectangle> aRects(mpIMEInfos->nLen);
 
                auto CollectCharPositions = [&](const LineAreaInfo& rInfo) {
                    if (!rInfo.pLine) // Start of ParaPortion
                    {
                        if (rInfo.nPortion < nPortionPos)
                            return CallbackResult::SkipThisPortion;
                        if (rInfo.nPortion > nPortionPos)
                            return CallbackResult::Stop;
                        assert(&rInfo.rPortion == pParaPortion);
                    }
                    else // This is the needed ParaPortion
                    {
                        if (rInfo.pLine->GetStart() > nMaxPos)
                            return CallbackResult::Stop;
                        if (rInfo.pLine->GetEnd() < nMinPos)
                            return CallbackResult::Continue;
                        for (sal_Int32 n = nMinPos; n <= nMaxPos; ++n)
                        {
                            if (rInfo.pLine->IsIn(n))
                            {
                                tools::Rectangle aR = GetEditCursor(*pParaPortion, *rInfo.pLine, n, CursorFlags());
                                aR.Move(getTopLeftDocOffset(rInfo.aArea));
                                aRects[n - nMinPos] = pView->getImpl().GetWindowPos(aR);
                            }
                        }
                    }
                    return CallbackResult::Continue;
                };
                IterateLineAreas(CollectCharPositions, IterFlag::none);
 
                if (vcl::Window* pWindow = pView->GetWindow())
                    pWindow->SetCompositionCharRect(aRects.data(), aRects.size());
            }
        }
    }
    else
        bConsumed = false;
 
    return GetSelEngine().Command(rCEvt) || bConsumed;
}
 
bool ImpEditEngine::MouseButtonUp( const MouseEvent& rMEvt, EditView* pView )
{
    GetSelEngine().SetCurView( pView );
    GetSelEngine().SelMouseButtonUp( rMEvt );
 
    // in the tiled rendering case, setting bInSelection here has unexpected
    // consequences - further tiles painting removes the selection
    // FIXME I believe resetting bInSelection should not be here even in the
    // non-tiled-rendering case, but it has been here since 2000 (and before)
    // so who knows what corner case it was supposed to solve back then
    if (!comphelper::LibreOfficeKit::isActive())
        mbInSelection = false;
 
    // Special treatments
    EditSelection aCurSel( pView->getImpl().GetEditSelection() );
    if ( aCurSel.HasRange() )
        return true;
 
    if ( ( rMEvt.GetClicks() != 1 ) || !rMEvt.IsLeft() || rMEvt.IsMod2() )
        return true;
 
    const OutputDevice& rOutDev = pView->getEditViewCallbacks() ? pView->getEditViewCallbacks()->EditViewOutputDevice() : *pView->GetWindow()->GetOutDev();
    Point aLogicClick = rOutDev.PixelToLogic(rMEvt.GetPosPixel());
    const SvxFieldItem* pFld = pView->GetField(aLogicClick);
    if (!pFld)
        return true;
 
    // tdf#121039 When in edit mode, editeng is responsible for opening the URL on mouse click
    bool bUrlOpened = GetEditEnginePtr()->FieldClicked( *pFld );
    if (bUrlOpened)
        return true;
 
    if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFld->GetField()))
    {
        bool bCtrlClickHappened = rMEvt.IsMod1();
        bool bCtrlClickSecOption
            = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink);
        if ((bCtrlClickHappened && bCtrlClickSecOption)
            || (!bCtrlClickHappened && !bCtrlClickSecOption))
        {
            css::uno::Reference<css::system::XSystemShellExecute> exec(
                css::system::SystemShellExecute::create(
                    comphelper::getProcessComponentContext()));
            exec->execute(pUrlField->GetURL(), OUString(),
                          css::system::SystemShellExecuteFlags::DEFAULTS);
        }
    }
    return true;
}
 
void ImpEditEngine::ReleaseMouse()
{
    GetSelEngine().ReleaseMouse();
}
 
bool ImpEditEngine::MouseMove( const MouseEvent& rMEvt, EditView* pView )
{
    // MouseMove is called directly after ShowQuickHelp()!
    GetSelEngine().SetCurView( pView );
    GetSelEngine().SelMouseMove( rMEvt );
    return true;
}
 
EditPaM ImpEditEngine::InsertText(const EditSelection& aSel, const OUString& rStr)
{
    EditPaM aPaM = ImpInsertText( aSel, rStr );
    return aPaM;
}
 
void ImpEditEngine::Clear()
{
    InitDoc( false );
 
    EditPaM aPaM = maEditDoc.GetStartPaM();
    EditSelection aSel( aPaM );
 
    mnCurTextHeight = 0;
 
    ResetUndoManager();
 
    for (size_t nView = maEditViews.size(); nView; )
    {
        EditView* pView = maEditViews[--nView];
        pView->getImpl().SetEditSelection( aSel );
    }
 
    // Related: tdf#82115 Fix crash when handling input method events.
    // The nodes in mpIMEInfos may be deleted in ImpEditEngine::Clear() which
    // causes a crash in the CommandEventId::ExtTextInput and
    // CommandEventId::EndExtTextInput event handlers.
    mpIMEInfos.reset();
}
 
EditPaM ImpEditEngine::RemoveText()
{
    InitDoc( true );
 
    EditPaM aStartPaM = maEditDoc.GetStartPaM();
    EditSelection aEmptySel( aStartPaM, aStartPaM );
    for (EditView* pView : maEditViews)
    {
        pView->getImpl().SetEditSelection( aEmptySel );
    }
    ResetUndoManager();
    return maEditDoc.GetStartPaM();
}
 
 
void ImpEditEngine::SetText(const OUString& rText)
{
    // RemoveText deletes the undo list!
    EditPaM aStartPaM = RemoveText();
    bool bUndoCurrentlyEnabled = IsUndoEnabled();
    // The text inserted manually can not be made reversible by the user
    EnableUndo( false );
 
    EditSelection aEmptySel( aStartPaM, aStartPaM );
    EditPaM aPaM = aStartPaM;
    if (!rText.isEmpty())
        aPaM = ImpInsertText( aEmptySel, rText );
 
    for (EditView* pView : maEditViews)
    {
        pView->getImpl().SetEditSelection( EditSelection( aPaM, aPaM ) );
        //  If no text then also no Format&Update
        // => The text remains.
        if (rText.isEmpty() && IsUpdateLayout())
        {
            tools::Rectangle aTmpRect( pView->GetOutputArea().TopLeft(),
                                Size( maPaperSize.Width(), mnCurTextHeight ) );
            aTmpRect.Intersection( pView->GetOutputArea() );
            pView->InvalidateWindow( aTmpRect );
        }
    }
    if (rText.isEmpty())     // otherwise it must be invalidated later, !bFormatted is enough.
        mnCurTextHeight = 0;
    EnableUndo( bUndoCurrentlyEnabled );
    OSL_ENSURE( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "Undo after SetText?" );
}
 
 
const SfxItemSet& ImpEditEngine::GetEmptyItemSet() const
{
    if ( !pEmptyItemSet )
    {
        pEmptyItemSet = std::make_unique<SfxItemSetFixed<EE_ITEMS_START, EE_ITEMS_END>>(const_cast<SfxItemPool&>(maEditDoc.GetItemPool()));
        for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++)
        {
            pEmptyItemSet->ClearItem( nWhich );
        }
    }
    return *pEmptyItemSet;
}
 
 
//  MISC
 
void ImpEditEngine::TextModified()
{
    mbFormatted = false;
 
    if ( GetNotifyHdl().IsSet() )
    {
        EENotify aNotify( EE_NOTIFY_TEXTMODIFIED );
        GetNotifyHdl().Call( aNotify );
    }
}
 
 
void ImpEditEngine::ParaAttribsChanged( ContentNode const * pNode, bool bIgnoreUndoCheck )
{
    assert(pNode && "ParaAttribsChanged: Which one?");
 
    maEditDoc.SetModified( true );
    mbFormatted = false;
 
    ParaPortion* pPortion = FindParaPortion( pNode );
    assert(pPortion);
    pPortion->MarkSelectionInvalid( 0 );
 
    sal_Int32 nPara = maEditDoc.GetPos( pNode );
    assert( nPara != EE_PARA_NOT_FOUND );
    if (bIgnoreUndoCheck || mpEditEngine->IsInUndo())
        mpEditEngine->ParaAttribsChanged( nPara );
 
    ParaPortion* pNextPortion = GetParaPortions().SafeGetObject( nPara+1 );
    // => is formatted again anyway, if Invalid.
    if ( pNextPortion && !pNextPortion->IsInvalid() )
        CalcHeight(*pNextPortion);
}
 
 
//  Cursor movements
 
 
EditSelection const & ImpEditEngine::MoveCursor( const KeyEvent& rKeyEvent, EditView* pEditView )
{
    // Actually, only necessary for up/down, but whatever.
    CheckIdleFormatter();
 
    EditPaM aPaM(pEditView->getImpl().GetEditSelection().Max());
 
    EditPaM aOldPaM( aPaM );
 
    TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom;
    if (IsEffectivelyVertical() && IsTopToBottom())
        eTextDirection = TextDirectionality::TopToBottom_RightToLeft;
    else if (IsEffectivelyVertical() && !IsTopToBottom())
        eTextDirection = TextDirectionality::BottomToTop_LeftToRight;
    else if ( IsRightToLeft( GetEditDoc().GetPos( aPaM.GetNode() ) ) )
        eTextDirection = TextDirectionality::RightToLeft_TopToBottom;
 
    KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection );
 
    bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1();
    sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode();
 
    if ( DoVisualCursorTraveling() )
    {
        // Only for simple cursor movement...
        if ( !bCtrl && ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) ) )
        {
            aPaM = CursorVisualLeftRight( pEditView, aPaM, rKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL, rKeyEvent.GetKeyCode().GetCode() == KEY_LEFT );
            nCode = 0;  // skip switch statement
        }
    }
 
    bool bKeyModifySelection = aTranslatedKeyEvent.GetKeyCode().IsShift();
    switch ( nCode )
    {
        case KEY_UP:        aPaM = CursorUp( aPaM, pEditView );
                            break;
        case KEY_DOWN:      aPaM = CursorDown( aPaM, pEditView );
                            break;
        case KEY_LEFT:      aPaM = bCtrl ? WordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL );
                            break;
        case KEY_RIGHT:     aPaM = bCtrl ? WordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL );
                            break;
        case KEY_HOME:      aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM );
                            break;
        case KEY_END:       aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM );
                            break;
        case KEY_PAGEUP:    aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM, pEditView );
                            break;
        case KEY_PAGEDOWN:  aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM, pEditView );
                            break;
        case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
                            aPaM = CursorStartOfLine( aPaM );
                            bKeyModifySelection = false;
                            break;
        case css::awt::Key::MOVE_TO_END_OF_LINE:
                            aPaM = CursorEndOfLine( aPaM );
                            bKeyModifySelection = false;
                            break;
        case css::awt::Key::MOVE_WORD_BACKWARD:
                            aPaM = WordLeft( aPaM );
                            bKeyModifySelection = false;
                            break;
        case css::awt::Key::MOVE_WORD_FORWARD:
                            aPaM = WordRight( aPaM );
                            bKeyModifySelection = false;
                            break;
        case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
                            aPaM = CursorStartOfParagraph( aPaM );
                            if( aPaM == aOldPaM )
                            {
                                aPaM = CursorLeft( aPaM );
                                aPaM = CursorStartOfParagraph( aPaM );
                            }
                            bKeyModifySelection = false;
                            break;
        case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
                            aPaM = CursorEndOfParagraph( aPaM );
                            if( aPaM == aOldPaM )
                            {
                                aPaM = CursorRight( aPaM );
                                aPaM = CursorEndOfParagraph( aPaM );
                            }
                            bKeyModifySelection = false;
                            break;
        case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
                            aPaM = CursorStartOfDoc();
                            bKeyModifySelection = false;
                            break;
        case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
                            aPaM = CursorEndOfDoc();
                            bKeyModifySelection = false;
                            break;
        case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
                            aPaM = CursorStartOfLine( aPaM );
                            bKeyModifySelection = true;
                            break;
        case css::awt::Key::SELECT_TO_END_OF_LINE:
                            aPaM = CursorEndOfLine( aPaM );
                            bKeyModifySelection = true;
                            break;
        case css::awt::Key::SELECT_BACKWARD:
                            aPaM = CursorLeft( aPaM );
                            bKeyModifySelection = true;
                            break;
        case css::awt::Key::SELECT_FORWARD:
                            aPaM = CursorRight( aPaM );
                            bKeyModifySelection = true;
                            break;
        case css::awt::Key::SELECT_WORD_BACKWARD:
                            aPaM = WordLeft( aPaM );
                            bKeyModifySelection = true;
                            break;
        case css::awt::Key::SELECT_WORD_FORWARD:
                            aPaM = WordRight( aPaM );
                            bKeyModifySelection = true;
                            break;
        case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
                            aPaM = CursorStartOfParagraph( aPaM );
                            if( aPaM == aOldPaM )
                            {
                                aPaM = CursorLeft( aPaM );
                                aPaM = CursorStartOfParagraph( aPaM );
                            }
                            bKeyModifySelection = true;
                            break;
        case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
                            aPaM = CursorEndOfParagraph( aPaM );
                            if( aPaM == aOldPaM )
                            {
                                aPaM = CursorRight( aPaM );
                                aPaM = CursorEndOfParagraph( aPaM );
                            }
                            bKeyModifySelection = true;
                            break;
        case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
                            aPaM = CursorStartOfDoc();
                            bKeyModifySelection = true;
                            break;
        case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
                            aPaM = CursorEndOfDoc();
                            bKeyModifySelection = true;
                            break;
    }
 
    if ( aOldPaM != aPaM && nullptr != aOldPaM.GetNode() )
    {
        aOldPaM.GetNode()->checkAndDeleteEmptyAttribs();
    }
 
    // May cause, a CreateAnchor or deselection all
    maSelEngine.SetCurView(pEditView);
    maSelEngine.CursorPosChanging( bKeyModifySelection, aTranslatedKeyEvent.GetKeyCode().IsMod1() );
    EditPaM aOldEnd(pEditView->getImpl().GetEditSelection().Max());
 
    {
        EditSelection aNewSelection(pEditView->getImpl().GetEditSelection());
        aNewSelection.Max() = aPaM;
        pEditView->getImpl().SetEditSelection(aNewSelection);
        // const_cast<EditPaM&>(pEditView->getImpl().GetEditSelection().Max()) = aPaM;
    }
 
    if ( bKeyModifySelection )
    {
        // Then the selection is expanded ... or the whole selection is painted in case of tiled rendering.
        EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? pEditView->getImpl().GetEditSelection().Min() : aOldEnd, aPaM );
        pEditView->getImpl().DrawSelectionXOR( aTmpNewSel );
    }
    else
    {
        EditSelection aNewSelection(pEditView->getImpl().GetEditSelection());
        aNewSelection.Min() = aPaM;
        pEditView->getImpl().SetEditSelection(aNewSelection);
        // const_cast<EditPaM&>(pEditView->getImpl().GetEditSelection().Min()) = aPaM;
    }
 
    return pEditView->getImpl().GetEditSelection();
}
 
EditPaM ImpEditEngine::CursorVisualStartEnd( EditView const * mpEditView, const EditPaM& rPaM, bool bStart )
{
    EditPaM aPaM( rPaM );
 
    sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() );
    ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
    if (!pParaPortion)
        return aPaM;
 
    sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false );
    const EditLine& rLine = pParaPortion->GetLines()[nLine];
    bool bEmptyLine = rLine.GetStart() == rLine.GetEnd();
 
    mpEditView->getImpl().maExtraCursorFlags = CursorFlags();
 
    if ( !bEmptyLine )
    {
        OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart());
 
        UErrorCode nError = U_ZERO_ERROR;
        UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError );
 
        const UBiDiLevel  nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/;
        ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError );
 
        sal_Int32 nVisPos = bStart ? 0 : aLine.getLength()-1;
        const sal_Int32 nLogPos = ubidi_getLogicalIndex( pBidi, nVisPos, &nError );
 
        ubidi_close( pBidi );
 
        aPaM.SetIndex( nLogPos + rLine.GetStart() );
 
        sal_Int32 nTmp;
        sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTmp, true );
        const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
        bool bPortionRTL = rTextPortion.IsRightToLeft();
 
        if ( bStart )
        {
            mpEditView->getImpl().SetCursorBidiLevel( bPortionRTL ? 0 : 1 );
            // Maybe we must be *behind* the character
            if (bPortionRTL && mpEditView->IsInsertMode())
                aPaM.SetIndex( aPaM.GetIndex()+1 );
        }
        else
        {
            mpEditView->getImpl().SetCursorBidiLevel( bPortionRTL ? 1 : 0 );
            if ( !bPortionRTL && mpEditView->IsInsertMode() )
                aPaM.SetIndex( aPaM.GetIndex()+1 );
        }
    }
 
    return aPaM;
}
 
EditPaM ImpEditEngine::CursorVisualLeftRight( EditView const * pEditView, const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode, bool bVisualToLeft )
{
    EditPaM aPaM( rPaM );
 
    sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() );
    ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
    if (!pParaPortion)
        return aPaM;
 
    sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false );
    const EditLine& rLine = pParaPortion->GetLines()[nLine];
    bool bEmptyLine = rLine.GetStart() == rLine.GetEnd();
 
    pEditView->getImpl().maExtraCursorFlags = CursorFlags();
 
    bool bParaRTL = IsRightToLeft( nPara );
 
    bool bDone = false;
 
    if ( bEmptyLine )
    {
        if ( bVisualToLeft )
        {
            aPaM = CursorUp( aPaM, pEditView );
            if ( aPaM != rPaM )
                aPaM = CursorVisualStartEnd( pEditView, aPaM, false );
        }
        else
        {
            aPaM = CursorDown( aPaM, pEditView );
            if ( aPaM != rPaM )
                aPaM = CursorVisualStartEnd( pEditView, aPaM, true );
        }
 
        bDone = true;
    }
 
    bool bLogicalBackward = bParaRTL ? !bVisualToLeft : bVisualToLeft;
 
    if ( !bDone && pEditView->IsInsertMode() )
    {
        // Check if we are within a portion and don't have overwrite mode, then it's easy...
        sal_Int32 nPortionStart;
        sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart );
        const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
 
        bool bPortionBoundary = ( aPaM.GetIndex() == nPortionStart ) || ( aPaM.GetIndex() == (nPortionStart+rTextPortion.GetLen()) );
        sal_uInt16 nRTLLevel = rTextPortion.GetRightToLeftLevel();
 
        // Portion boundary doesn't matter if both have same RTL level
        sal_Int32 nRTLLevelNextPortion = -1;
        if ( bPortionBoundary && aPaM.GetIndex() && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ) )
        {
            sal_Int32 nTmp;
            sal_Int32 nNextTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex()+1, nTmp, !bLogicalBackward );
            const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nNextTextPortion];
            nRTLLevelNextPortion = rNextTextPortion.GetRightToLeftLevel();
        }
 
        if ( !bPortionBoundary || ( nRTLLevel == nRTLLevelNextPortion ) )
        {
            if (bVisualToLeft != bool(nRTLLevel % 2))
            {
                aPaM = CursorLeft( aPaM, nCharacterIteratorMode );
                pEditView->getImpl().SetCursorBidiLevel( 1 );
            }
            else
            {
                aPaM = CursorRight( aPaM, nCharacterIteratorMode );
                pEditView->getImpl().SetCursorBidiLevel( 0 );
            }
            bDone = true;
        }
    }
 
    if ( !bDone )
    {
        bool bGotoStartOfNextLine = false;
        bool bGotoEndOfPrevLine = false;
 
        OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart());
        const sal_Int32 nPosInLine = aPaM.GetIndex() - rLine.GetStart();
 
        UErrorCode nError = U_ZERO_ERROR;
        UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError );
 
        const UBiDiLevel  nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/;
        ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError );
 
        if ( !pEditView->IsInsertMode() )
        {
            bool bEndOfLine = nPosInLine == aLine.getLength();
            sal_Int32 nVisPos = ubidi_getVisualIndex( pBidi, !bEndOfLine ? nPosInLine : nPosInLine-1, &nError );
            if ( bVisualToLeft )
            {
                bGotoEndOfPrevLine = nVisPos == 0;
                if ( !bEndOfLine )
                    nVisPos--;
            }
            else
            {
                bGotoStartOfNextLine = nVisPos == (aLine.getLength() - 1);
                if ( !bEndOfLine )
                    nVisPos++;
            }
 
            if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine )
            {
                aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) );
                pEditView->getImpl().SetCursorBidiLevel( 0 );
            }
        }
        else
        {
            bool bWasBehind = false;
            bool bBeforePortion = !nPosInLine || pEditView->getImpl().GetCursorBidiLevel() == 1;
            if ( nPosInLine && ( !bBeforePortion ) ) // before the next portion
                bWasBehind = true;  // step one back, otherwise visual will be unusable when rtl portion follows.
 
            sal_Int32 nPortionStart;
            sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, bBeforePortion );
            const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
            bool bRTLPortion = rTextPortion.IsRightToLeft();
 
            // -1: We are 'behind' the character
            tools::Long nVisPos = static_cast<tools::Long>(ubidi_getVisualIndex( pBidi, bWasBehind ? nPosInLine-1 : nPosInLine, &nError ));
            if ( bVisualToLeft )
            {
                if ( !bWasBehind || bRTLPortion )
                    nVisPos--;
            }
            else
            {
                if ( bWasBehind || bRTLPortion || bBeforePortion )
                    nVisPos++;
            }
 
            bGotoEndOfPrevLine = nVisPos < 0;
            bGotoStartOfNextLine = nVisPos >= aLine.getLength();
 
            if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine )
            {
                aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) );
 
                // RTL portion, stay visually on the left side.
                sal_Int32 _nPortionStart;
                // sal_uInt16 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, !bRTLPortion );
                sal_Int32 _nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), _nPortionStart, true );
                const TextPortion& _rTextPortion = pParaPortion->GetTextPortions()[_nTextPortion];
                if ( bVisualToLeft && !bRTLPortion && _rTextPortion.IsRightToLeft() )
                    aPaM.SetIndex( aPaM.GetIndex()+1 );
                else if ( !bVisualToLeft && bRTLPortion && ( bWasBehind || !_rTextPortion.IsRightToLeft() ) )
                    aPaM.SetIndex( aPaM.GetIndex()+1 );
 
                pEditView->getImpl().SetCursorBidiLevel( _nPortionStart );
            }
        }
 
        ubidi_close( pBidi );
 
        if ( bGotoEndOfPrevLine )
        {
            aPaM = CursorUp( aPaM, pEditView );
            if ( aPaM != rPaM )
                aPaM = CursorVisualStartEnd( pEditView, aPaM, false );
        }
        else if ( bGotoStartOfNextLine )
        {
            aPaM = CursorDown( aPaM, pEditView );
            if ( aPaM != rPaM )
                aPaM = CursorVisualStartEnd( pEditView, aPaM, true );
        }
    }
    return aPaM;
}
 
 
EditPaM ImpEditEngine::CursorLeft( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
{
    EditPaM aCurPaM( rPaM );
    EditPaM aNewPaM( aCurPaM );
 
    if ( aCurPaM.GetIndex() )
    {
        sal_Int32 nCount = 1;
        uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
        aNewPaM.SetIndex(
             _xBI->previousCharacters(
                 aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount));
    }
    else
    {
        ContentNode* pNode = aCurPaM.GetNode();
        pNode = GetPrevVisNode( pNode );
        if ( pNode )
        {
            aNewPaM.SetNode( pNode );
            aNewPaM.SetIndex( pNode->Len() );
        }
    }
 
    return aNewPaM;
}
 
EditPaM ImpEditEngine::CursorRight( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
{
    EditPaM aCurPaM( rPaM );
    EditPaM aNewPaM( aCurPaM );
 
    if ( aCurPaM.GetIndex() < aCurPaM.GetNode()->Len() )
    {
        uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
        sal_Int32 nCount = 1;
        aNewPaM.SetIndex(
            _xBI->nextCharacters(
                aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount));
    }
    else
    {
        ContentNode* pNode = aCurPaM.GetNode();
        pNode = GetNextVisNode( pNode );
        if ( pNode )
        {
            aNewPaM.SetNode( pNode );
            aNewPaM.SetIndex( 0 );
        }
    }
 
    return aNewPaM;
}
 
EditPaM ImpEditEngine::CursorUp( const EditPaM& rPaM, EditView const * pView )
{
    assert(pView && "No View - No Cursor Movement!");
 
    const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() );
    assert(pPPortion);
    sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() );
    assert(nLine >= 0);
    const EditLine& rLine = pPPortion->GetLines()[nLine];
 
    tools::Long nX;
    if ( pView->getImpl().mnTravelXPos == TRAVEL_X_DONTKNOW )
    {
        nX = GetXPos(*pPPortion, rLine, rPaM.GetIndex());
        pView->getImpl().mnTravelXPos = nX + mnOnePixelInRef;
    }
    else
        nX = pView->getImpl().mnTravelXPos;
 
    EditPaM aNewPaM( rPaM );
    if ( nLine )    // same paragraph
    {
        assert(nLine >= 1);
        const EditLine& rPrevLine = pPPortion->GetLines()[nLine-1];
        aNewPaM.SetIndex(GetChar(*pPPortion, rPrevLine, nX));
        // If a previous automatically wrapped line, and one has to be exactly
        // at the end of this line, the cursor lands on the current line at the
        // beginning. See Problem: Last character of an automatically wrapped
        // Row = cursor
        if ( aNewPaM.GetIndex() && ( aNewPaM.GetIndex() == rLine.GetStart() ) )
            aNewPaM = CursorLeft( aNewPaM );
    }
    else    // previous paragraph
    {
        const ParaPortion* pPrevPortion = GetPrevVisPortion( pPPortion );
        if ( pPrevPortion )
        {
            const EditLine& rLine2 = pPrevPortion->GetLines()[pPrevPortion->GetLines().Count()-1];
            aNewPaM.SetNode( pPrevPortion->GetNode() );
            aNewPaM.SetIndex(GetChar(*pPrevPortion, rLine2, nX + mnOnePixelInRef));
        }
    }
 
    return aNewPaM;
}
 
EditPaM ImpEditEngine::CursorDown( const EditPaM& rPaM, EditView const * pView )
{
    assert(pView);
 
    const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() );
    assert(pPPortion);
    sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() );
 
    tools::Long nX;
    if ( pView->getImpl().mnTravelXPos == TRAVEL_X_DONTKNOW )
    {
        const EditLine& rLine = pPPortion->GetLines()[nLine];
        nX = GetXPos(*pPPortion, rLine, rPaM.GetIndex());
        pView->getImpl().mnTravelXPos = nX + mnOnePixelInRef;
    }
    else
        nX = pView->getImpl().mnTravelXPos;
 
    EditPaM aNewPaM( rPaM );
    if ( nLine < pPPortion->GetLines().Count()-1 )
    {
        const EditLine& rNextLine = pPPortion->GetLines()[nLine+1];
        aNewPaM.SetIndex(GetChar(*pPPortion, rNextLine, nX));
        // Special treatment, see CursorUp ...
        if ( ( aNewPaM.GetIndex() == rNextLine.GetEnd() ) && ( aNewPaM.GetIndex() > rNextLine.GetStart() ) && ( aNewPaM.GetIndex() < pPPortion->GetNode()->Len() ) )
            aNewPaM = CursorLeft( aNewPaM );
    }
    else    // next paragraph
    {
        const ParaPortion* pNextPortion = GetNextVisPortion( pPPortion );
        if ( pNextPortion )
        {
            const EditLine& rLine = pNextPortion->GetLines()[0];
            aNewPaM.SetNode( pNextPortion->GetNode() );
            // Never at the very end when several lines, because then a line
            // below the cursor appears.
            aNewPaM.SetIndex(GetChar(*pNextPortion, rLine, nX + mnOnePixelInRef));
            if ( ( aNewPaM.GetIndex() == rLine.GetEnd() ) && ( aNewPaM.GetIndex() > rLine.GetStart() ) && ( pNextPortion->GetLines().Count() > 1 ) )
                aNewPaM = CursorLeft( aNewPaM );
        }
    }
 
    return aNewPaM;
}
 
EditPaM ImpEditEngine::CursorStartOfLine( const EditPaM& rPaM )
{
    const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() );
    assert(pCurPortion);
    sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() );
    const EditLine& rLine = pCurPortion->GetLines()[nLine];
 
    EditPaM aNewPaM( rPaM );
    aNewPaM.SetIndex( rLine.GetStart() );
    return aNewPaM;
}
 
EditPaM ImpEditEngine::CursorEndOfLine( const EditPaM& rPaM )
{
    const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() );
    assert(pCurPortion);
    sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() );
    const EditLine& rLine = pCurPortion->GetLines()[nLine];
 
    EditPaM aNewPaM( rPaM );
    aNewPaM.SetIndex( rLine.GetEnd() );
    if ( rLine.GetEnd() > rLine.GetStart() )
    {
        if ( aNewPaM.GetNode()->IsFeature( aNewPaM.GetIndex() - 1 ) )
        {
            // When a soft break, be in front of it!
            const EditCharAttrib* pNextFeature = aNewPaM.GetNode()->GetCharAttribs().FindFeature( aNewPaM.GetIndex()-1 );
            if ( pNextFeature && ( pNextFeature->GetItem()->Which() == EE_FEATURE_LINEBR ) )
                aNewPaM = CursorLeft( aNewPaM );
        }
        else if ( ( aNewPaM.GetNode()->GetChar( aNewPaM.GetIndex() - 1 ) == ' ' ) && ( aNewPaM.GetIndex() != aNewPaM.GetNode()->Len() ) )
        {
            // For a Blank in an auto wrapped line, it makes sense, to stand
            // in front of it, since the user wants to be after the word.
            // If this is changed, special treatment for Pos1 to End!
            aNewPaM = CursorLeft( aNewPaM );
        }
    }
    return aNewPaM;
}
 
EditPaM ImpEditEngine::CursorStartOfParagraph( const EditPaM& rPaM )
{
    EditPaM aPaM(rPaM);
    aPaM.SetIndex(0);
    return aPaM;
}
 
EditPaM ImpEditEngine::CursorEndOfParagraph( const EditPaM& rPaM )
{
    EditPaM aPaM(rPaM);
    aPaM.SetIndex(rPaM.GetNode()->Len());
    return aPaM;
}
 
EditPaM ImpEditEngine::CursorStartOfDoc()
{
    EditPaM aPaM( maEditDoc.GetObject( 0 ), 0 );
    return aPaM;
}
 
EditPaM ImpEditEngine::CursorEndOfDoc()
{
    ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1 );
    ParaPortion* pLastPortion = GetParaPortions().SafeGetObject( maEditDoc.Count()-1 );
    OSL_ENSURE( pLastNode && pLastPortion, "CursorEndOfDoc: Node or Portion not found" );
    if (!(pLastNode && pLastPortion))
        return EditPaM();
 
    if ( !pLastPortion->IsVisible() )
    {
        pLastNode = GetPrevVisNode( pLastPortion->GetNode() );
        OSL_ENSURE( pLastNode, "No visible paragraph?" );
        if ( !pLastNode )
            pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1 );
    }
 
    EditPaM aPaM( pLastNode, pLastNode->Len() );
    return aPaM;
}
 
EditPaM ImpEditEngine::PageUp( const EditPaM& rPaM, EditView const * pView )
{
    tools::Rectangle aRect = PaMtoEditCursor( rPaM );
    Point aTopLeft = aRect.TopLeft();
    aTopLeft.AdjustY( -(pView->GetVisArea().GetHeight() *9/10) );
    aTopLeft.AdjustX(mnOnePixelInRef);
    if ( aTopLeft.Y() < 0 )
    {
        aTopLeft.setY( 0 );
    }
    return GetPaM( aTopLeft );
}
 
EditPaM ImpEditEngine::PageDown( const EditPaM& rPaM, EditView const * pView )
{
    tools::Rectangle aRect = PaMtoEditCursor( rPaM );
    Point aBottomRight = aRect.BottomRight();
    aBottomRight.AdjustY(pView->GetVisArea().GetHeight() *9/10 );
    aBottomRight.AdjustX(mnOnePixelInRef);
    tools::Long nHeight = GetTextHeight();
    if ( aBottomRight.Y() > nHeight )
    {
        aBottomRight.setY( nHeight-2 );
    }
    return GetPaM( aBottomRight );
}
 
EditPaM ImpEditEngine::WordLeft( const EditPaM& rPaM )
{
    const sal_Int32 nCurrentPos = rPaM.GetIndex();
    EditPaM aNewPaM( rPaM );
    if ( nCurrentPos == 0 )
    {
        // Previous paragraph...
        sal_Int32 nCurPara = maEditDoc.GetPos( aNewPaM.GetNode() );
        ContentNode* pPrevNode = maEditDoc.GetObject( --nCurPara );
        if ( pPrevNode )
        {
            aNewPaM.SetNode( pPrevNode );
            aNewPaM.SetIndex( pPrevNode->Len() );
        }
    }
    else
    {
        // we need to increase the position by 1 when retrieving the locale
        // since the attribute for the char left to the cursor position is returned
        EditPaM aTmpPaM( aNewPaM );
        if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
            aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
        lang::Locale aLocale( GetLocale( aTmpPaM ) );
 
        uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
        i18n::Boundary aBoundary =
            _xBI->getWordBoundary(aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true);
        if ( aBoundary.startPos >= nCurrentPos )
            aBoundary = _xBI->previousWord(
                aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES);
        aNewPaM.SetIndex( ( aBoundary.startPos != -1 ) ? aBoundary.startPos : 0 );
    }
 
    return aNewPaM;
}
 
EditPaM ImpEditEngine::WordRight( const EditPaM& rPaM, sal_Int16 nWordType )
{
    const sal_Int32 nMax = rPaM.GetNode()->Len();
    EditPaM aNewPaM( rPaM );
    if ( aNewPaM.GetIndex() < nMax )
    {
        // we need to increase the position by 1 when retrieving the locale
        // since the attribute for the char left to the cursor position is returned
        EditPaM aTmpPaM( aNewPaM );
        aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
        lang::Locale aLocale( GetLocale( aTmpPaM ) );
 
        uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
        i18n::Boundary aBoundary = _xBI->nextWord(
            aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), aLocale, nWordType);
        aNewPaM.SetIndex( aBoundary.startPos );
    }
    // not 'else', maybe the index reached nMax now...
    if ( aNewPaM.GetIndex() >= nMax )
    {
        // Next paragraph ...
        sal_Int32 nCurPara = maEditDoc.GetPos( aNewPaM.GetNode() );
        ContentNode* pNextNode = maEditDoc.GetObject( ++nCurPara );
        if ( pNextNode )
        {
            aNewPaM.SetNode( pNextNode );
            aNewPaM.SetIndex( 0 );
        }
    }
    return aNewPaM;
}
 
EditPaM ImpEditEngine::StartOfWord( const EditPaM& rPaM )
{
    EditPaM aNewPaM( rPaM );
 
    // we need to increase the position by 1 when retrieving the locale
    // since the attribute for the char left to the cursor position is returned
    EditPaM aTmpPaM( aNewPaM );
    if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
        aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
    lang::Locale aLocale( GetLocale( aTmpPaM ) );
 
    uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
    // tdf#135761 - since this function is only used when a selection is deleted at the left,
    // change the search preference of the word boundary from forward to backward.
    // For further details of a deletion of a selection check ImpEditEngine::DeleteLeftOrRight.
    i18n::Boundary aBoundary = _xBI->getWordBoundary(
        rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, false);
 
    aNewPaM.SetIndex( aBoundary.startPos );
    return aNewPaM;
}
 
EditPaM ImpEditEngine::EndOfWord( const EditPaM& rPaM )
{
    EditPaM aNewPaM( rPaM );
 
    // we need to increase the position by 1 when retrieving the locale
    // since the attribute for the char left to the cursor position is returned
    EditPaM aTmpPaM( aNewPaM );
    if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
        aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
    lang::Locale aLocale( GetLocale( aTmpPaM ) );
 
    uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
    i18n::Boundary aBoundary = _xBI->getWordBoundary(
        rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true);
 
    aNewPaM.SetIndex( aBoundary.endPos );
    return aNewPaM;
}
 
EditSelection ImpEditEngine::SelectWord( const EditSelection& rCurSel, sal_Int16 nWordType, bool bAcceptStartOfWord, bool bAcceptEndOfWord )
{
    EditSelection aNewSel( rCurSel );
    EditPaM aPaM( rCurSel.Max() );
 
    // we need to increase the position by 1 when retrieving the locale
    // since the attribute for the char left to the cursor position is returned
    EditPaM aTmpPaM( aPaM );
    if ( aTmpPaM.GetIndex() < aPaM.GetNode()->Len() )
        aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
    lang::Locale aLocale( GetLocale( aTmpPaM ) );
 
    uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
    sal_Int16 nType = _xBI->getWordType(
        aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale);
 
    if ( nType == i18n::WordType::ANY_WORD )
    {
        i18n::Boundary aBoundary = _xBI->getWordBoundary(
            aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale, nWordType,
            aPaM.GetNode()->GetString().getLength() == aPaM.GetIndex() ? false : true);
 
        // don't select when cursor at end of word
        if ( ( aBoundary.endPos > aPaM.GetIndex() || ( bAcceptEndOfWord && aBoundary.endPos == aPaM.GetIndex() ) ) &&
             ( ( aBoundary.startPos < aPaM.GetIndex() ) || ( bAcceptStartOfWord && ( aBoundary.startPos == aPaM.GetIndex() ) ) ) )
        {
            aNewSel.Min().SetIndex( aBoundary.startPos );
            aNewSel.Max().SetIndex( aBoundary.endPos );
        }
    }
 
    return aNewSel;
}
 
EditSelection ImpEditEngine::SelectSentence( const EditSelection& rCurSel )
    const
{
    uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
    const EditPaM& rPaM = rCurSel.Min();
    const ContentNode* pNode = rPaM.GetNode();
    // #i50710# line breaks are marked with 0x01 - the break iterator prefers 0x0a for that
    const OUString sParagraph = pNode->GetString().replaceAll("\x01", "\x0a");
    //return Null if search starts at the beginning of the string
    sal_Int32 nStart = rPaM.GetIndex() ? _xBI->beginOfSentence( sParagraph, rPaM.GetIndex(), GetLocale( rPaM ) ) : 0;
 
    sal_Int32 nEnd = _xBI->endOfSentence(
        pNode->GetString(), rPaM.GetIndex(), GetLocale(rPaM));
 
    EditSelection aNewSel( rCurSel );
    OSL_ENSURE(pNode->Len() ? (nStart < pNode->Len()) : (nStart == 0), "sentence start index out of range");
    OSL_ENSURE(nEnd <= pNode->Len(), "sentence end index out of range");
    aNewSel.Min().SetIndex( nStart );
    aNewSel.Max().SetIndex( nEnd );
    return aNewSel;
}
 
bool ImpEditEngine::IsInputSequenceCheckingRequired( sal_Unicode nChar, const EditSelection& rCurSel ) const
{
    uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
 
    // get the index that really is first
    const sal_Int32 nFirstPos = std::min(rCurSel.Min().GetIndex(), rCurSel.Max().GetIndex());
 
    bool bIsSequenceChecking =
        SvtCTLOptions::IsCTLFontEnabled() &&
        SvtCTLOptions::IsCTLSequenceChecking() &&
        nFirstPos != 0 && /* first char needs not to be checked */
        _xBI.is() && i18n::ScriptType::COMPLEX == _xBI->getScriptType( OUString( nChar ), 0 );
 
    return bIsSequenceChecking;
}
 
static  bool lcl_HasStrongLTR ( std::u16string_view rTxt, sal_Int32 nStart, sal_Int32 nEnd )
 {
     for( sal_Int32 nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx )
     {
         const UCharDirection nCharDir = u_charDirection ( rTxt[ nCharIdx ] );
         if ( nCharDir == U_LEFT_TO_RIGHT ||
              nCharDir == U_LEFT_TO_RIGHT_EMBEDDING ||
              nCharDir == U_LEFT_TO_RIGHT_OVERRIDE )
             return true;
     }
     return false;
 }
 
 
void ImpEditEngine::InitScriptTypes( sal_Int32 nPara )
{
    ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
    if (!pParaPortion)
        return;
 
    ScriptTypePosInfos& rTypes = pParaPortion->getScriptTypePosInfos();
    rTypes.clear();
 
    ContentNode* pNode = pParaPortion->GetNode();
    if ( !pNode->Len() )
        return;
 
    uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
 
    OUString aText = pNode->GetString();
 
    // To handle fields put the character from the field in the string,
    // because endOfScript( ... ) will skip the CH_FEATURE, because this is WEAK
    const EditCharAttrib* pField = pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, 0 );
    while ( pField )
    {
        const OUString aFldText = static_cast<const EditCharAttribField*>(pField)->GetFieldValue();
        if ( !aFldText.isEmpty() )
        {
            aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(0,1) );
            short nFldScriptType = _xBI->getScriptType( aFldText, 0 );
 
            for ( sal_Int32 nCharInField = 1; nCharInField < aFldText.getLength(); nCharInField++ )
            {
                short nTmpType = _xBI->getScriptType( aFldText, nCharInField );
 
                // First char from field wins...
                if ( nFldScriptType == i18n::ScriptType::WEAK )
                {
                    nFldScriptType = nTmpType;
                    aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) );
                }
 
                // ...  but if the first one is LATIN, and there are CJK or CTL chars too,
                // we prefer that ScriptType because we need another font.
                if ( ( nTmpType == i18n::ScriptType::ASIAN ) || ( nTmpType == i18n::ScriptType::COMPLEX ) )
                {
                    aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) );
                    break;
                }
            }
        }
        // #112831# Last Field might go from 0xffff to 0x0000
        pField = pField->GetEnd() ? pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, pField->GetEnd() ) : nullptr;
    }
 
    sal_Int32 nTextLen = aText.getLength();
 
    sal_Int32 nPos = 0;
    short nScriptType = _xBI->getScriptType( aText, nPos );
    rTypes.emplace_back( nScriptType, nPos, nTextLen );
    nPos = _xBI->endOfScript( aText, nPos, nScriptType );
    while ( ( nPos != -1 ) && ( nPos < nTextLen ) )
    {
        rTypes.back().nEndPos = nPos;
 
        nScriptType = _xBI->getScriptType( aText, nPos );
        tools::Long nEndPos = _xBI->endOfScript( aText, nPos, nScriptType );
 
        if ( ( nScriptType == i18n::ScriptType::WEAK ) || ( nScriptType == rTypes.back().nScriptType ) )
        {
            // Expand last ScriptTypePosInfo, don't create weak or unnecessary portions
            rTypes.back().nEndPos = nEndPos;
        }
        else
        {
            auto nPrevPos = nPos;
            auto nPrevChar = aText.iterateCodePoints(&nPrevPos, -1);
            if (_xBI->getScriptType(aText, nPrevPos) == i18n::ScriptType::WEAK)
            {
                auto nChar = aText.iterateCodePoints(&nPos, 0);
                auto nType = unicode::getUnicodeType(nChar);
                if (nType == css::i18n::UnicodeType::NON_SPACING_MARK ||
                    nType == css::i18n::UnicodeType::ENCLOSING_MARK ||
                    nType == css::i18n::UnicodeType::COMBINING_SPACING_MARK ||
                    (nPrevChar == 0x202F /* NNBSP, tdf#112594 */ &&
                     u_getIntPropertyValue(nChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN))
                {
                    rTypes.back().nEndPos = nPos = nPrevPos;
                    break;
                }
            }
            rTypes.emplace_back( nScriptType, nPos, nTextLen );
        }
 
        nPos = nEndPos;
    }
 
    if ( rTypes[0].nScriptType == i18n::ScriptType::WEAK )
        rTypes[0].nScriptType = ( rTypes.size() > 1 ) ? rTypes[1].nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() );
 
    // create writing direction information:
    WritingDirectionInfos& rDirInfos = pParaPortion->getWritingDirectionInfos();
    if (rDirInfos.empty())
        InitWritingDirections( nPara );
 
    // i89825: Use CTL font for numbers embedded into an RTL run:
    for (const WritingDirectionInfo & rDirInfo : rDirInfos)
    {
        const sal_Int32 nStart = rDirInfo.nStartPos;
        const sal_Int32 nEnd   = rDirInfo.nEndPos;
        const sal_uInt8 nCurrDirType = rDirInfo.nType;
 
        if ( nCurrDirType % 2 == UBIDI_RTL  || // text in RTL run
            ( nCurrDirType > UBIDI_LTR && !lcl_HasStrongLTR( aText, nStart, nEnd ) ) ) // non-strong text in embedded LTR run
        {
            size_t nIdx = 0;
 
            // Skip entries in ScriptArray which are not inside the RTL run:
            while ( nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart )
                ++nIdx;
 
            // Remove any entries *inside* the current run:
            while (nIdx < rTypes.size() && rTypes[nIdx].nEndPos <= nEnd)
            {
                // coverity[use_iterator] - we're protected from a bad iterator by the above condition
                rTypes.erase(rTypes.begin() + nIdx);
            }
 
            // special case:
            if(nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart && rTypes[nIdx].nEndPos > nEnd)
            {
                rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( rTypes[nIdx].nScriptType, nEnd, rTypes[nIdx].nEndPos ) );
                rTypes[nIdx].nEndPos = nStart;
            }
 
            if( nIdx )
                rTypes[nIdx - 1].nEndPos = nStart;
 
            rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( i18n::ScriptType::COMPLEX, nStart, nEnd) );
            ++nIdx;
 
            if( nIdx < rTypes.size() )
                rTypes[nIdx].nStartPos = nEnd;
        }
    }
}
 
namespace {
 
struct FindByPos
{
    explicit FindByPos(sal_Int32 nPos)
        : mnPos(nPos)
    {
    }
 
    bool operator()(const ScriptTypePosInfos::value_type& rValue)
    {
        return rValue.nStartPos <= mnPos && rValue.nEndPos >= mnPos;
    }
 
private:
    sal_Int32 mnPos;
};
 
}
 
sal_uInt16 ImpEditEngine::GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEndPos ) const
{
    sal_uInt16 nScriptType = 0;
 
    if ( pEndPos )
        *pEndPos = rPaM.GetNode()->Len();
 
    if ( rPaM.GetNode()->Len() )
    {
        sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() );
        const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
        if (pParaPortion)
        {
            const ScriptTypePosInfos& rTypes = pParaPortion->getScriptTypePosInfos();
 
            if (rTypes.empty())
                const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
 
            const sal_Int32 nPos = rPaM.GetIndex();
            ScriptTypePosInfos::const_iterator itr = std::find_if(rTypes.begin(), rTypes.end(), FindByPos(nPos));
            if(itr != rTypes.end())
            {
                nScriptType = itr->nScriptType;
                if( pEndPos )
                    *pEndPos = itr->nEndPos;
            }
        }
    }
    return nScriptType ? nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() );
}
 
SvtScriptType ImpEditEngine::GetScriptType( const ESelection& rSel )
{
    EditSelection aSel(CreateSel(rSel));
    return GetItemScriptType( aSel );
}
 
SvtScriptType ImpEditEngine::GetItemScriptType( const EditSelection& rSel ) const
{
    EditSelection aSel( rSel );
    aSel.Adjust( maEditDoc );
 
    SvtScriptType nScriptType = SvtScriptType::NONE;
 
    sal_Int32 nStartPara = GetEditDoc().GetPos( aSel.Min().GetNode() );
    sal_Int32 nEndPara = GetEditDoc().GetPos( aSel.Max().GetNode() );
 
    for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ )
    {
        const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
        if (!pParaPortion)
            continue;
 
        ScriptTypePosInfos const& rTypes = pParaPortion->getScriptTypePosInfos();
 
        if (rTypes.empty())
            const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
 
        // find all the scripts of this range
        sal_Int32 nS = ( nPara == nStartPara ) ? aSel.Min().GetIndex() : 0;
        sal_Int32 nE = ( nPara == nEndPara ) ? aSel.Max().GetIndex() : pParaPortion->GetNode()->Len();
 
        //no selection, just bare cursor
        if (nStartPara == nEndPara && nS == nE)
        {
            //If we are not at the start of the paragraph we want the properties of the
            //preceding character. Otherwise get the properties of the next (or what the
            //next would have if it existed)
            if (nS != 0)
                --nS;
            else
                ++nE;
        }
 
        for (const ScriptTypePosInfo & rType : rTypes)
        {
            bool bStartInRange = rType.nStartPos <= nS && nS < rType.nEndPos;
            bool bEndInRange = rType.nStartPos < nE && nE <= rType.nEndPos;
 
            if (bStartInRange || bEndInRange)
            {
                if ( rType.nScriptType != i18n::ScriptType::WEAK )
                    nScriptType |= SvtLanguageOptions::FromI18NToSvtScriptType( rType.nScriptType );
            }
        }
    }
    return bool(nScriptType) ? nScriptType : SvtLanguageOptions::GetScriptTypeOfLanguage( GetDefaultLanguage() );
}
 
bool ImpEditEngine::IsScriptChange( const EditPaM& rPaM ) const
{
    bool bScriptChange = false;
 
    if ( rPaM.GetNode()->Len() )
    {
        sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() );
        const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
        if (pParaPortion)
        {
            ScriptTypePosInfos const& rTypes = pParaPortion->getScriptTypePosInfos();
 
            if (rTypes.empty())
                const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
 
            const sal_Int32 nPos = rPaM.GetIndex();
            for (const ScriptTypePosInfo & rType : rTypes)
            {
                if ( rType.nStartPos == nPos )
                {
                    bScriptChange = true;
                    break;
                }
            }
        }
    }
    return bScriptChange;
}
 
bool ImpEditEngine::HasScriptType( sal_Int32 nPara, sal_uInt16 nType ) const
{
    bool bTypeFound = false;
 
    const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
    if (pParaPortion)
    {
        const ScriptTypePosInfos& rTypes = pParaPortion->getScriptTypePosInfos();
 
        if (rTypes.empty())
            const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
 
        for ( size_t n = rTypes.size(); n && !bTypeFound; )
        {
            if ( rTypes[--n].nScriptType == nType )
                bTypeFound = true;
        }
    }
    return bTypeFound;
}
 
void ImpEditEngine::InitWritingDirections( sal_Int32 nPara )
{
    ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
    if (!pParaPortion)
        return;
 
    WritingDirectionInfos& rInfos = pParaPortion->getWritingDirectionInfos();
    rInfos.clear();
 
    if (pParaPortion->GetNode()->Len() && !mbFuzzing)
    {
        const OUString aText = pParaPortion->GetNode()->GetString();
 
        // Bidi functions from icu 2.0
 
        UErrorCode nError = U_ZERO_ERROR;
        UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError );
        nError = U_ZERO_ERROR;
 
        const UBiDiLevel nBidiLevel = IsRightToLeft(nPara) ? 1 /*RTL*/ : 0 /*LTR*/;
        ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError );
        nError = U_ZERO_ERROR;
 
        int32_t nCount = ubidi_countRuns( pBidi, &nError );
 
        /* ubidi_countRuns can return -1 in case of error */
        if (nCount > 0)
        {
            int32_t nStart = 0;
            int32_t nEnd;
            UBiDiLevel nCurrDir;
 
            for (int32_t nIdx = 0; nIdx < nCount; ++nIdx)
            {
                ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
                rInfos.emplace_back( nCurrDir, nStart, nEnd );
                nStart = nEnd;
            }
        }
 
        ubidi_close( pBidi );
    }
 
    // No infos mean ubidi error, default to LTR
    if ( rInfos.empty() )
        rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->Len() );
 
}
 
bool ImpEditEngine::IsRightToLeft( sal_Int32 nPara ) const
{
    bool bR2L = false;
    const SvxFrameDirectionItem* pFrameDirItem = nullptr;
 
    if ( !IsEffectivelyVertical() )
    {
        bR2L = GetDefaultHorizontalTextDirection() == EEHorizontalTextDirection::R2L;
        pFrameDirItem = &GetParaAttrib( nPara, EE_PARA_WRITINGDIR );
        if ( pFrameDirItem->GetValue() == SvxFrameDirection::Environment )
        {
            // #103045# if DefaultHorizontalTextDirection is set, use that value, otherwise pool default.
            if ( GetDefaultHorizontalTextDirection() != EEHorizontalTextDirection::Default )
            {
                pFrameDirItem = nullptr; // bR2L already set to default horizontal text direction
            }
            else
            {
                // Use pool default
                pFrameDirItem = &GetEmptyItemSet().Get(EE_PARA_WRITINGDIR);
            }
        }
    }
 
    if ( pFrameDirItem )
        bR2L = pFrameDirItem->GetValue() == SvxFrameDirection::Horizontal_RL_TB;
 
    return bR2L;
}
 
bool ImpEditEngine::HasDifferentRTLLevels( const ContentNode* pNode )
{
    bool bHasDifferentRTLLevels = false;
 
    sal_Int32 nPara = GetEditDoc().GetPos( pNode );
    ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
    if (pParaPortion)
    {
        sal_uInt16 nRTLLevel = IsRightToLeft( nPara ) ? 1 : 0;
        for ( sal_Int32 n = 0; n < pParaPortion->GetTextPortions().Count(); n++ )
        {
            const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n];
            if ( rTextPortion.GetRightToLeftLevel() != nRTLLevel )
            {
                bHasDifferentRTLLevels = true;
                break;
            }
        }
    }
    return bHasDifferentRTLLevels;
}
 
 
sal_uInt8 ImpEditEngine::GetRightToLeft( sal_Int32 nPara, sal_Int32 nPos, sal_Int32* pStart, sal_Int32* pEnd )
{
    sal_uInt8 nRightToLeft = 0;
 
    ContentNode* pNode = maEditDoc.GetObject( nPara );
    if ( pNode && pNode->Len() )
    {
        ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
        if (pParaPortion)
        {
            WritingDirectionInfos& rDirInfos = pParaPortion->getWritingDirectionInfos();
            if (rDirInfos.empty())
                InitWritingDirections( nPara );
 
            for (const WritingDirectionInfo & rDirInfo : rDirInfos)
            {
                if ( ( rDirInfo.nStartPos <= nPos ) && ( rDirInfo.nEndPos >= nPos ) )
                {
                    nRightToLeft = rDirInfo.nType;
                    if ( pStart )
                        *pStart = rDirInfo.nStartPos;
                    if ( pEnd )
                        *pEnd = rDirInfo.nEndPos;
                    break;
                }
            }
        }
    }
    return nRightToLeft;
}
 
SvxAdjust ImpEditEngine::GetJustification( sal_Int32 nPara ) const
{
    SvxAdjust eJustification = SvxAdjust::Left;
 
    if (!maStatus.IsOutliner())
    {
        eJustification = GetParaAttrib( nPara, EE_PARA_JUST ).GetAdjust();
 
        if ( IsRightToLeft( nPara ) )
        {
            if ( eJustification == SvxAdjust::Left )
                eJustification = SvxAdjust::Right;
            else if ( eJustification == SvxAdjust::Right )
                eJustification = SvxAdjust::Left;
        }
    }
    return eJustification;
}
 
SvxCellJustifyMethod ImpEditEngine::GetJustifyMethod( sal_Int32 nPara ) const
{
    const SvxJustifyMethodItem& rItem = GetParaAttrib(nPara, EE_PARA_JUST_METHOD);
    return static_cast<SvxCellJustifyMethod>(rItem.GetEnumValue());
}
 
SvxCellVerJustify ImpEditEngine::GetVerJustification( sal_Int32 nPara ) const
{
    const SvxVerJustifyItem& rItem = GetParaAttrib(nPara, EE_PARA_VER_JUST);
    return static_cast<SvxCellVerJustify>(rItem.GetEnumValue());
}
 
//  Text changes
void ImpEditEngine::ImpRemoveChars( const EditPaM& rPaM, sal_Int32 nChars )
{
    if ( IsUndoEnabled() && !IsInUndo() )
    {
        const OUString aStr( rPaM.GetNode()->Copy( rPaM.GetIndex(), nChars ) );
 
        // Check whether attributes are deleted or changed:
        const sal_Int32 nStart = rPaM.GetIndex();
        const sal_Int32 nEnd = nStart + nChars;
        const CharAttribList::AttribsType& rAttribs = rPaM.GetNode()->GetCharAttribs().GetAttribs();
        for (const auto & rAttrib : rAttribs)
        {
            const EditCharAttrib& rAttr = *rAttrib;
            if (rAttr.GetEnd() >= nStart && rAttr.GetStart() < nEnd)
            {
                EditSelection aSel( rPaM );
                aSel.Max().SetIndex( aSel.Max().GetIndex() + nChars );
                InsertUndo( CreateAttribUndo( aSel, GetEmptyItemSet() ) );
                break;  // for
            }
        }
        InsertUndo(std::make_unique<EditUndoRemoveChars>(mpEditEngine, CreateEPaM(rPaM), aStr));
    }
 
    maEditDoc.RemoveChars( rPaM, nChars );
}
 
EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 nNewPos )
{
    aOldPositions.Normalize();
    bool bValidAction = ( static_cast<tools::Long>(nNewPos) < aOldPositions.Min() ) || ( static_cast<tools::Long>(nNewPos) > aOldPositions.Max() );
    OSL_ENSURE( bValidAction, "Move in itself?" );
    OSL_ENSURE( aOldPositions.Max() <= static_cast<tools::Long>(GetParaPortions().Count()), "totally over it: MoveParagraphs" );
 
    EditSelection aSelection;
 
    if ( !bValidAction )
    {
        aSelection = maEditDoc.GetStartPaM();
        return aSelection;
    }
 
    sal_Int32 nParaCount = GetParaPortions().Count();
 
    if ( nNewPos >= nParaCount )
        nNewPos = nParaCount;
 
    // Height may change when moving first or last Paragraph
    ParaPortion* pRecalc1 = nullptr;
    ParaPortion* pRecalc2 = nullptr;
    ParaPortion* pRecalc3 = nullptr;
    ParaPortion* pRecalc4 = nullptr;
 
    if (nNewPos == 0) // Move to Start
    {
        if (GetParaPortions().exists(0))
            pRecalc1 = &GetParaPortions().getRef(0);
        if (GetParaPortions().exists(aOldPositions.Min()))
            pRecalc2 = &GetParaPortions().getRef(aOldPositions.Min());
 
    }
    else if (nNewPos == nParaCount)
    {
        if (GetParaPortions().exists(nParaCount - 1))
            pRecalc1 = &GetParaPortions().getRef(nParaCount - 1);
        if (GetParaPortions().exists(aOldPositions.Max()))
            pRecalc2 = &GetParaPortions().getRef(aOldPositions.Max());
    }
 
    if (aOldPositions.Min() == 0) // Move from Start
    {
        if (GetParaPortions().exists(0))
            pRecalc3 = &GetParaPortions().getRef(0);
        if (GetParaPortions().exists(aOldPositions.Max() + 1))
            pRecalc4 = &GetParaPortions().getRef(aOldPositions.Max() + 1);
    }
    else if (aOldPositions.Max() == nParaCount - 1)
    {
        if (GetParaPortions().exists(aOldPositions.Max()))
            pRecalc3 = &GetParaPortions().getRef(aOldPositions.Max());
        if (GetParaPortions().exists(aOldPositions.Min() - 1))
            pRecalc4 = &GetParaPortions().getRef(aOldPositions.Min() - 1);
    }
 
    MoveParagraphsInfo aMoveParagraphsInfo( aOldPositions.Min(), aOldPositions.Max(), nNewPos );
    maBeginMovingParagraphsHdl.Call( aMoveParagraphsInfo );
 
    if ( IsUndoEnabled() && !IsInUndo())
        InsertUndo(std::make_unique<EditUndoMoveParagraphs>(mpEditEngine, aOldPositions, nNewPos));
 
    // do not lose sight of the Position !
    ParaPortion* pDestPortion = GetParaPortions().SafeGetObject( nNewPos );
 
    // Temporary containers used for moving the paragraph portions and content nodes to a new location
    std::vector<std::unique_ptr<ParaPortion>> aParagraphPortionVector;
    std::vector<std::unique_ptr<ContentNode>> aContentNodeVector;
 
    // Take the paragraph portions and content nodes out of its containers
    for (tools::Long i = aOldPositions.Min(); i <= aOldPositions.Max(); i++  )
    {
        // always aOldPositions.Min() as the index, since we remove and the elements from the containers and the
        // other elements shift to the left.
        std::unique_ptr<ParaPortion> pPortion = GetParaPortions().Release(aOldPositions.Min());
        aParagraphPortionVector.push_back(std::move(pPortion));
 
        std::unique_ptr<ContentNode> pContentNode = maEditDoc.Release(aOldPositions.Min());
        aContentNodeVector.push_back(std::move(pContentNode));
    }
 
    // Determine the new location for paragraphs
    sal_Int32 nRealNewPos = pDestPortion ? GetParaPortions().GetPos( pDestPortion ) : GetParaPortions().Count();
    assert( nRealNewPos != EE_PARA_NOT_FOUND && "ImpMoveParagraphs: Invalid Position!" );
 
    // Add the paragraph portions and content nodes to a new position
    sal_Int32 i = 0;
    for (auto& pPortion : aParagraphPortionVector)
    {
        if (i == 0)
            aSelection.Min().SetNode(pPortion->GetNode());
        aSelection.Max().SetNode(pPortion->GetNode());
        aSelection.Max().SetIndex(pPortion->GetNode()->Len());
 
        maEditDoc.Insert(nRealNewPos + i, std::move(aContentNodeVector[i]));
        GetParaPortions().Insert(nRealNewPos + i, std::move(pPortion));
 
        ++i;
    }
 
    // Signal end of paragraph moving
    maEndMovingParagraphsHdl.Call( aMoveParagraphsInfo );
 
    if ( GetNotifyHdl().IsSet() )
    {
        EENotify aNotify( EE_NOTIFY_PARAGRAPHSMOVED );
        aNotify.nParagraph = nNewPos;
        aNotify.nParam1 = aOldPositions.Min();
        aNotify.nParam2 = aOldPositions.Max();
        GetNotifyHdl().Call( aNotify );
    }
 
    maEditDoc.SetModified( true );
 
    if (pRecalc1)
        CalcHeight(*pRecalc1);
    if (pRecalc2)
        CalcHeight(*pRecalc2);
    if (pRecalc3)
        CalcHeight(*pRecalc3);
    if (pRecalc4)
        CalcHeight(*pRecalc4);
 
#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
    ParaPortionList::DbgCheck(GetParaPortions(), maEditDoc);
#endif
    return aSelection;
}
 
 
EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward )
{
    OSL_ENSURE( pLeft != pRight, "Join together the same paragraph ?" );
    OSL_ENSURE( maEditDoc.GetPos( pLeft ) != EE_PARA_NOT_FOUND, "Inserted node not found (1)" );
    OSL_ENSURE( maEditDoc.GetPos( pRight ) != EE_PARA_NOT_FOUND, "Inserted node not found (2)" );
 
    // #i120020# it is possible that left and right are *not* in the desired order (left/right)
    // so correct it. This correction is needed, else an invalid SfxLinkUndoAction will be
    // created from ConnectParagraphs below. Assert this situation, it should be corrected by the
    // caller.
    if (maEditDoc.GetPos( pLeft ) > maEditDoc.GetPos( pRight ))
    {
        OSL_ENSURE(false, "ImpConnectParagraphs with wrong order of pLeft/pRight nodes (!)");
        std::swap(pLeft, pRight);
    }
 
    sal_Int32 nParagraphTobeDeleted = maEditDoc.GetPos( pRight );
    maDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pRight, nParagraphTobeDeleted ));
 
    GetEditEnginePtr()->ParagraphConnected( maEditDoc.GetPos( pLeft ), maEditDoc.GetPos( pRight ) );
 
    if ( IsUndoEnabled() && !IsInUndo() )
    {
        InsertUndo( std::make_unique<EditUndoConnectParas>(mpEditEngine,
            maEditDoc.GetPos( pLeft ), pLeft->Len(),
            pLeft->GetContentAttribs().GetItems(), pRight->GetContentAttribs().GetItems(),
            pLeft->GetStyleSheet(), pRight->GetStyleSheet(), bBackward ) );
    }
 
    if ( bBackward )
    {
        pLeft->SetStyleSheet( pRight->GetStyleSheet() );
        // it feels wrong to set pLeft's attribs if pRight is empty, tdf#128046
        if ( pRight->Len() )
            pLeft->GetContentAttribs().GetItems().Set( pRight->GetContentAttribs().GetItems() );
        pLeft->GetCharAttribs().GetDefFont() = pRight->GetCharAttribs().GetDefFont();
    }
 
    ParaAttribsChanged( pLeft, true );
 
    // First search for Portions since pRight is gone after ConnectParagraphs.
    ParaPortion* pLeftPortion = FindParaPortion( pLeft );
    assert(pLeftPortion);
 
    if ( GetStatus().DoOnlineSpelling() )
    {
        sal_Int32 nEnd = pLeft->Len();
        sal_Int32 nInv = nEnd ? nEnd-1 : nEnd;
        pLeft->GetWrongList()->ClearWrongs( nInv, static_cast<size_t>(-1), pLeft );  // Possibly remove one
        pLeft->GetWrongList()->SetInvalidRange(nInv, nEnd+1);
        // Take over misspelled words
        WrongList* pRWrongs = pRight->GetWrongList();
        for (auto & elem : *pRWrongs)
        {
            if (elem.mnStart != 0)   // Not a subsequent
            {
                elem.mnStart = elem.mnStart + nEnd;
                elem.mnEnd = elem.mnEnd + nEnd;
                pLeft->GetWrongList()->push_back(elem);
            }
        }
    }
 
    if ( IsCallParaInsertedOrDeleted() )
        GetEditEnginePtr()->ParagraphDeleted( nParagraphTobeDeleted );
 
    EditPaM aPaM = maEditDoc.ConnectParagraphs( pLeft, pRight );
    GetParaPortions().Remove( nParagraphTobeDeleted );
 
    pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() );
 
    // the right node is deleted by EditDoc:ConnectParagraphs().
    if ( GetTextRanger() )
    {
        // By joining together the two, the left is although reformatted,
        // however if its height does not change then the formatting receives
        // the change of the total text height too late...
        for ( sal_Int32 n = nParagraphTobeDeleted; n < GetParaPortions().Count(); n++ )
        {
            ParaPortion& rParaPortion = GetParaPortions().getRef(n);
            rParaPortion.MarkSelectionInvalid(0);
            rParaPortion.GetLines().Reset();
        }
    }
 
    TextModified();
 
    return aPaM;
}
 
EditPaM ImpEditEngine::DeleteLeftOrRight( const EditSelection& rSel, sal_uInt8 nMode, DeleteMode nDelMode )
{
    OSL_ENSURE( !rSel.DbgIsBuggy( maEditDoc ), "Index out of range in DeleteLeftOrRight" );
 
    if ( rSel.HasRange() )  // only then Delete Selection
        return ImpDeleteSelection( rSel );
 
    EditPaM aCurPos( rSel.Max() );
    EditPaM aDelStart( aCurPos );
    EditPaM aDelEnd( aCurPos );
    if ( nMode == DEL_LEFT )
    {
        if ( nDelMode == DeleteMode::Simple )
        {
            sal_uInt16 nCharMode = i18n::CharacterIteratorMode::SKIPCHARACTER;
            // If we are deleting a variation selector, we want to delete the
            // whole sequence (cell).
            sal_Int32 nIndex = aCurPos.GetIndex();
            if (nIndex > 0)
            {
                const OUString& rString = aCurPos.GetNode()->GetString();
                sal_Int32 nCode = rString.iterateCodePoints(&nIndex, -1);
                if (unicode::isVariationSelector(nCode))
                    nCharMode = i18n::CharacterIteratorMode::SKIPCELL;
            }
            aDelStart = CursorLeft(aCurPos, nCharMode);
        }
        else if ( nDelMode == DeleteMode::RestOfWord )
        {
            aDelStart = StartOfWord( aCurPos );
            if ( aDelStart.GetIndex() == aCurPos.GetIndex() )
                aDelStart = WordLeft( aCurPos );
        }
        else    // DELMODE_RESTOFCONTENT
        {
            aDelStart.SetIndex( 0 );
            if ( aDelStart == aCurPos )
            {
                // Complete paragraph previous
                ContentNode* pPrev = GetPrevVisNode( aCurPos.GetNode() );
                if ( pPrev )
                    aDelStart = EditPaM( pPrev, 0 );
            }
        }
    }
    else
    {
        if ( nDelMode == DeleteMode::Simple )
        {
            aDelEnd = CursorRight( aCurPos );
        }
        else if ( nDelMode == DeleteMode::RestOfWord )
        {
            aDelEnd = EndOfWord( aCurPos );
            if (aDelEnd.GetIndex() == aCurPos.GetIndex())
            {
                const sal_Int32 nLen(aCurPos.GetNode()->Len());
                // end of para?
                if (aDelEnd.GetIndex() == nLen)
                {
                    ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() );
                    if ( pNext )
                        aDelEnd = EditPaM( pNext, 0 );
                }
                else // there's still something to delete on the right
                {
                    aDelEnd = EndOfWord( WordRight( aCurPos ) );
                }
            }
        }
        else    // DELMODE_RESTOFCONTENT
        {
            aDelEnd.SetIndex( aCurPos.GetNode()->Len() );
            if ( aDelEnd == aCurPos )
            {
                // Complete paragraph next
                ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() );
                if ( pNext )
                    aDelEnd = EditPaM( pNext, pNext->Len() );
            }
        }
    }
 
    // ConnectParagraphs not enough for different Nodes when
    // DeleteMode::RestOfContent.
    if ( ( nDelMode == DeleteMode::RestOfContent ) || ( aDelStart.GetNode() == aDelEnd.GetNode() ) )
        return ImpDeleteSelection( EditSelection( aDelStart, aDelEnd ) );
 
    return ImpConnectParagraphs(aDelStart.GetNode(), aDelEnd.GetNode());
}
 
EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel)
{
    if ( !rCurSel.HasRange() )
        return rCurSel.Min();
 
    EditSelection aCurSel(rCurSel);
    aCurSel.Adjust( maEditDoc );
    EditPaM aStartPaM(aCurSel.Min());
    EditPaM aEndPaM(aCurSel.Max());
 
    if( nullptr != aStartPaM.GetNode() )
        aStartPaM.GetNode()->checkAndDeleteEmptyAttribs(); // only so that newly set Attributes disappear...
    if( nullptr != aEndPaM.GetNode() )
        aEndPaM.GetNode()->checkAndDeleteEmptyAttribs(); // only so that newly set Attributes disappear...
 
    OSL_ENSURE( aStartPaM.GetIndex() <= aStartPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" );
    OSL_ENSURE( aEndPaM.GetIndex() <= aEndPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" );
 
    sal_Int32 nStartNode = maEditDoc.GetPos( aStartPaM.GetNode() );
    sal_Int32 nEndNode = maEditDoc.GetPos( aEndPaM.GetNode() );
 
    assert( nEndNode != EE_PARA_NOT_FOUND && "Start > End ?!" );
    assert( nStartNode <= nEndNode && "Start > End ?!" );
 
    // Remove all nodes in between...
    for ( sal_Int32 z = nStartNode+1; z < nEndNode; z++ )
    {
        // Always nStartNode+1, due to Remove()!
        ImpRemoveParagraph( nStartNode+1 );
    }
 
    if ( aStartPaM.GetNode() != aEndPaM.GetNode() )
    {
        // The Rest of the StartNodes...
        ImpRemoveChars( aStartPaM, aStartPaM.GetNode()->Len() - aStartPaM.GetIndex() );
        ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() );
        assert(pPortion);
        pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() );
 
        // The beginning of the EndNodes...
        const sal_Int32 nChars = aEndPaM.GetIndex();
        aEndPaM.SetIndex( 0 );
        ImpRemoveChars( aEndPaM, nChars );
        pPortion = FindParaPortion( aEndPaM.GetNode() );
        assert(pPortion);
        pPortion->MarkSelectionInvalid( 0 );
        // Join together...
        aStartPaM = ImpConnectParagraphs( aStartPaM.GetNode(), aEndPaM.GetNode() );
    }
    else
    {
        ImpRemoveChars( aStartPaM, aEndPaM.GetIndex() - aStartPaM.GetIndex() );
        ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() );
        assert(pPortion);
        pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() );
    }
 
    UpdateSelections();
    TextModified();
    return aStartPaM;
}
 
void ImpEditEngine::RemoveParagraph( sal_Int32 nPara )
{
    DBG_ASSERT(maEditDoc.Count() > 1, "The first paragraph should not be deleted!");
    if (maEditDoc.Count() <= 1)
        return;
 
    ContentNode* pNode = maEditDoc.GetObject(nPara);
    const ParaPortion* pPortion = maParaPortionList.SafeGetObject(nPara);
    DBG_ASSERT( pPortion && pNode, "Paragraph not found: RemoveParagraph" );
    if ( pNode && pPortion )
    {
        // No Undo encapsulation needed.
        ImpRemoveParagraph(nPara);
        InvalidateFromParagraph(nPara);
        UpdateSelections();
        if (IsUpdateLayout())
            FormatAndLayout();
    }
}
 
void ImpEditEngine::ImpRemoveParagraph( sal_Int32 nPara )
{
    assert(maEditDoc.GetObject(nPara));
 
    ContentNode* pNextNode = maEditDoc.GetObject( nPara+1 );
 
    std::unique_ptr<ContentNode> pNode = maEditDoc.Release(nPara);
    maDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>(pNode.get(), nPara));
 
    // The node is managed by the undo and possibly destroyed!
 
    GetParaPortions().Remove( nPara );
 
    if ( IsCallParaInsertedOrDeleted() )
    {
        GetEditEnginePtr()->ParagraphDeleted( nPara );
    }
 
    // Extra-Space may be determined again in the following. For
    // ParaAttribsChanged the paragraph is unfortunately formatted again,
    // however this method should not be time critical!
    if ( pNextNode )
        ParaAttribsChanged( pNextNode );
 
    if (IsUndoEnabled() && !IsInUndo())
    {
        InsertUndo(std::make_unique<EditUndoDelContent>(mpEditEngine, std::move(pNode), nPara));
    }
    else
    {
        if ( pNode->GetStyleSheet() )
            EndListening(*pNode->GetStyleSheet());
        pNode.reset();
    }
}
 
EditPaM ImpEditEngine::AutoCorrect( const EditSelection& rCurSel, sal_Unicode c,
                                    bool bOverwrite, vcl::Window const * pFrameWin )
{
    // i.e. Calc has special needs regarding a leading single quotation mark
    // when starting cell input.
    if (c == '\'' && !IsReplaceLeadingSingleQuotationMark() &&
            rCurSel.Min() == rCurSel.Max() && rCurSel.Max().GetIndex() == 0)
    {
        return InsertTextUserInput( rCurSel, c, bOverwrite );
    }
 
    EditSelection aSel( rCurSel );
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    if ( pAutoCorrect )
    {
        if ( aSel.HasRange() )
            aSel = ImpDeleteSelection( rCurSel );
 
        // #i78661 allow application to turn off capitalization of
        // start sentence explicitly.
        // (This is done by setting IsFirstWordCapitalization to sal_False.)
        bool bOldCapitalStartSentence = pAutoCorrect->IsAutoCorrFlag( ACFlags::CapitalStartSentence );
        if (!IsFirstWordCapitalization())
        {
            ESelection aESel( CreateESel(aSel) );
            EditSelection aFirstWordSel;
            EditSelection aSecondWordSel;
            if (aESel.nEndPara == 0)    // is this the first para?
            {
                // select first word...
                // start by checking if para starts with word.
                aFirstWordSel = SelectWord( CreateSel(ESelection()) );
                if (aFirstWordSel.Min().GetIndex() == 0 && aFirstWordSel.Max().GetIndex() == 0)
                {
                    // para does not start with word -> select next/first word
                    EditPaM aRightWord( WordRight( aFirstWordSel.Max() ) );
                    aFirstWordSel = SelectWord( EditSelection( aRightWord ) );
                }
 
                // select second word
                // (sometimes aSel might not point to the end of the first word
                // but to some following char like '.'. ':', ...
                // In those cases we need aSecondWordSel to see if aSel
                // will actually effect the first word.)
                EditPaM aRight2Word( WordRight( aFirstWordSel.Max() ) );
                aSecondWordSel = SelectWord( EditSelection( aRight2Word ) );
            }
            bool bIsFirstWordInFirstPara = aESel.nEndPara == 0 &&
                    aFirstWordSel.Max().GetIndex() <= aSel.Max().GetIndex() &&
                    aSel.Max().GetIndex() <= aSecondWordSel.Min().GetIndex();
 
            if (bIsFirstWordInFirstPara)
                pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, IsFirstWordCapitalization() );
        }
 
        ContentNode* pNode = aSel.Max().GetNode();
        const sal_Int32 nIndex = aSel.Max().GetIndex();
        EdtAutoCorrDoc aAuto(mpEditEngine, pNode, nIndex, c);
        // FIXME: this _must_ be called with reference to the actual node text!
        OUString const& rNodeString(pNode->GetString());
        pAutoCorrect->DoAutoCorrect(
            aAuto, rNodeString, nIndex, c, !bOverwrite, mbNbspRunNext, pFrameWin );
        aSel.Max().SetIndex( aAuto.GetCursor() );
 
        // #i78661 since the SvxAutoCorrect object used here is
        // shared we need to reset the value to its original state.
        pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, bOldCapitalStartSentence );
    }
    return aSel.Max();
}
 
 
EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel,
        sal_Unicode c, bool bOverwrite )
{
    OSL_ENSURE( c != '\t', "Tab for InsertText ?" );
    OSL_ENSURE( c != '\n', "Word wrapping for InsertText ?");
 
    EditPaM aPaM( rCurSel.Min() );
 
    bool bDoOverwrite = bOverwrite &&
            ( aPaM.GetIndex() < aPaM.GetNode()->Len() );
 
    bool bUndoAction = ( rCurSel.HasRange() || bDoOverwrite );
 
    if ( bUndoAction )
        UndoActionStart( EDITUNDO_INSERT );
 
    if ( rCurSel.HasRange() )
    {
        aPaM = ImpDeleteSelection( rCurSel );
    }
    else if ( bDoOverwrite )
    {
        // If selected, then do not also overwrite a character!
        EditSelection aTmpSel( aPaM );
        aTmpSel.Max().SetIndex( aTmpSel.Max().GetIndex()+1 );
        OSL_ENSURE( !aTmpSel.DbgIsBuggy( maEditDoc ), "Overwrite: Wrong selection! ");
        ImpDeleteSelection( aTmpSel );
    }
 
    if ( aPaM.GetNode()->Len() < MAXCHARSINPARA )
    {
        if (IsInputSequenceCheckingRequired( c, rCurSel ))
        {
            uno::Reference < i18n::XExtendedInputSequenceChecker > _xISC( ImplGetInputSequenceChecker() );
 
            if (_xISC)
            {
                const sal_Int32 nTmpPos = aPaM.GetIndex();
                sal_Int16 nCheckMode = SvtCTLOptions::IsCTLSequenceCheckingRestricted() ?
                        i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
 
                // the text that needs to be checked is only the one
                // before the current cursor position
                const OUString aOldText( aPaM.GetNode()->Copy(0, nTmpPos) );
                OUString aNewText( aOldText );
                if (SvtCTLOptions::IsCTLSequenceCheckingTypeAndReplace())
                {
                    _xISC->correctInputSequence(aNewText, nTmpPos - 1, c, nCheckMode);
 
                    // find position of first character that has changed
                    sal_Int32 nOldLen = aOldText.getLength();
                    sal_Int32 nNewLen = aNewText.getLength();
                    const sal_Unicode *pOldTxt = aOldText.getStr();
                    const sal_Unicode *pNewTxt = aNewText.getStr();
                    sal_Int32 nChgPos = 0;
                    while ( nChgPos < nOldLen && nChgPos < nNewLen &&
                            pOldTxt[nChgPos] == pNewTxt[nChgPos] )
                        ++nChgPos;
 
                    const OUString aChgText( aNewText.copy( nChgPos ) );
 
                    // select text from first pos to be changed to current pos
                    EditSelection aSel( EditPaM( aPaM.GetNode(), nChgPos ), aPaM );
 
                    if (!aChgText.isEmpty())
                        return InsertText( aSel, aChgText ); // implicitly handles undo
                    else
                        return aPaM;
                }
                else
                {
                    // should the character be ignored (i.e. not get inserted) ?
                    if (!_xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode ))
                        return aPaM;    // nothing to be done -> no need for undo
                }
            }
 
            // at this point now we will insert the character 'normally' some lines below...
        }
 
        if ( IsUndoEnabled() && !IsInUndo() )
        {
            std::unique_ptr<EditUndoInsertChars> pNewUndo(new EditUndoInsertChars(mpEditEngine, CreateEPaM(aPaM), OUString(c)));
            bool bTryMerge = !bDoOverwrite && ( c != ' ' );
            InsertUndo( std::move(pNewUndo), bTryMerge );
        }
 
        maEditDoc.InsertText( aPaM, OUStringChar(c) );
        ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() );
        assert(pPortion);
        pPortion->MarkInvalid( aPaM.GetIndex(), 1 );
        aPaM.SetIndex( aPaM.GetIndex()+1 );   // does not do EditDoc-Method anymore
    }
 
    TextModified();
 
    if ( bUndoAction )
        UndoActionEnd();
 
    return aPaM;
}
 
EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUString& rStr)
{
    UndoActionStart( EDITUNDO_INSERT );
 
    EditPaM aPaM;
    if ( aCurSel.HasRange() )
        aPaM = ImpDeleteSelection( aCurSel );
    else
        aPaM = aCurSel.Max();
 
    EditPaM aCurPaM( aPaM );    // for the Invalidate
 
    // get word boundaries in order to clear possible WrongList entries
    // and invalidate all the necessary text (everything after and including the
    // start of the word)
    // #i107201# do the expensive SelectWord call only if online spelling is active
    EditSelection aCurWord;
    if ( GetStatus().DoOnlineSpelling() )
        aCurWord = SelectWord( EditSelection(aCurPaM), i18n::WordType::DICTIONARY_WORD );
 
    OUString aText(convertLineEnd(rStr, LINEEND_LF));
    if (mbFuzzing)    //tab expansion performance in editeng is appalling
        aText = aText.replaceAll("\t","-");
    SfxVoidItem aTabItem( EE_FEATURE_TAB );
 
    // Converts to linesep = \n
    // Token LINE_SEP query,
    // since the MAC-Compiler makes something else from \n !
 
    sal_Int32 nStart = 0;
    while ( nStart < aText.getLength() )
    {
        sal_Int32 nEnd = !maStatus.IsSingleLine() ?
            aText.indexOf( LINE_SEP, nStart ) : -1;
        if ( nEnd == -1 )
            nEnd = aText.getLength(); // not dereference!
 
        // Start == End => empty line
        if ( nEnd > nStart )
        {
            OUString aLine = aText.copy( nStart, nEnd-nStart );
            sal_Int32 nExistingChars = aPaM.GetNode()->Len();
            sal_Int32 nChars = nExistingChars + aLine.getLength();
            if (nChars > MAXCHARSINPARA)
            {
                sal_Int32 nMaxNewChars = std::max<sal_Int32>(0, MAXCHARSINPARA - nExistingChars);
                // Wherever we break, it may be wrong. However, try to find the
                // previous non-alnum/non-letter character. Note this is only
                // in the to be appended data, otherwise already existing
                // characters would have to be moved and PaM to be updated.
                // Restrict to 2*42, if not found by then assume other data or
                // language-script uses only letters or idiographs.
                sal_Int32 nPos = nMaxNewChars;
                while (nPos-- > 0 && (nMaxNewChars - nPos) <= 84)
                {
                    auto nNextPos = nPos;
                    const auto c = aLine.iterateCodePoints(&nNextPos);
                    switch (unicode::getUnicodeType(c))
                    {
                        case css::i18n::UnicodeType::UPPERCASE_LETTER:
                        case css::i18n::UnicodeType::LOWERCASE_LETTER:
                        case css::i18n::UnicodeType::TITLECASE_LETTER:
                        case css::i18n::UnicodeType::MODIFIER_LETTER:
                        case css::i18n::UnicodeType::OTHER_LETTER:
                        case css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER:
                        case css::i18n::UnicodeType::LETTER_NUMBER:
                        case css::i18n::UnicodeType::OTHER_NUMBER:
                        case css::i18n::UnicodeType::CURRENCY_SYMBOL:
                        break;
                        default:
                            {
                                // Ignore NO-BREAK spaces, NBSP, NNBSP, ZWNBSP.
                                if (c == 0x00A0 || c == 0x202F || c == 0xFEFF)
                                    break;
                                const auto n = aLine.iterateCodePoints(&nNextPos, 0);
                                if (c == '-' && nNextPos < nMaxNewChars)
                                {
                                    // Keep HYPHEN-MINUS with a number to the right.
                                    const sal_Int16 t = unicode::getUnicodeType(n);
                                    if (    t == css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER ||
                                            t == css::i18n::UnicodeType::LETTER_NUMBER ||
                                            t == css::i18n::UnicodeType::OTHER_NUMBER)
                                        nMaxNewChars = nPos;        // line break before
                                    else
                                        nMaxNewChars = nNextPos;    // line break after
                                }
                                else
                                {
                                    nMaxNewChars = nNextPos;        // line break after
                                }
                                nPos = 0;   // will break loop
                            }
                    }
                }
                // Remaining characters end up in the next paragraph. Note that
                // new nStart will be nEnd+1 below so decrement by one more.
                nEnd -= (aLine.getLength() - nMaxNewChars + 1);
                aLine = aLine.copy( 0, nMaxNewChars );  // Delete the Rest...
            }
            if ( IsUndoEnabled() && !IsInUndo() )
                InsertUndo(std::make_unique<EditUndoInsertChars>(mpEditEngine, CreateEPaM(aPaM), aLine));
            // Tabs ?
            if ( aLine.indexOf( '\t' ) == -1 )
                aPaM = maEditDoc.InsertText( aPaM, aLine );
            else
            {
                sal_Int32 nStart2 = 0;
                while ( nStart2 < aLine.getLength() )
                {
                    sal_Int32 nEnd2 = aLine.indexOf( "\t", nStart2 );
                    if ( nEnd2 == -1 )
                        nEnd2 = aLine.getLength();    // not dereference!
 
                    if ( nEnd2 > nStart2 )
                        aPaM = maEditDoc.InsertText( aPaM, aLine.copy( nStart2, nEnd2-nStart2 ) );
                    if ( nEnd2 < aLine.getLength() )
                    {
                        aPaM = maEditDoc.InsertFeature( aPaM, aTabItem );
                    }
                    nStart2 = nEnd2+1;
                }
            }
            ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() );
            assert(pPortion);
 
            if ( GetStatus().DoOnlineSpelling() )
            {
                // now remove the Wrongs (red spell check marks) from both words...
                WrongList *pWrongs = aCurPaM.GetNode()->GetWrongList();
                if (pWrongs && !pWrongs->empty())
                    pWrongs->ClearWrongs( aCurWord.Min().GetIndex(), aPaM.GetIndex(), aPaM.GetNode() );
                // ... and mark both words as 'to be checked again'
                pPortion->MarkInvalid( aCurWord.Min().GetIndex(), aLine.getLength() );
            }
            else
                pPortion->MarkInvalid( aCurPaM.GetIndex(), aLine.getLength() );
        }
        if ( nEnd < aText.getLength() )
            aPaM = ImpInsertParaBreak( aPaM );
 
        nStart = nEnd+1;
    }
 
    UndoActionEnd();
 
    TextModified();
    return aPaM;
}
 
EditPaM ImpEditEngine::ImpFastInsertText( EditPaM aPaM, const OUString& rStr )
{
    OSL_ENSURE( rStr.indexOf( 0x0A ) == -1, "FastInsertText: Newline not allowed! ");
    OSL_ENSURE( rStr.indexOf( 0x0D ) == -1, "FastInsertText: Newline not allowed! ");
    OSL_ENSURE( rStr.indexOf( '\t' ) == -1, "FastInsertText: Newline not allowed! ");
 
    if ( ( aPaM.GetNode()->Len() + rStr.getLength() ) < MAXCHARSINPARA )
    {
        if ( IsUndoEnabled() && !IsInUndo() )
            InsertUndo(std::make_unique<EditUndoInsertChars>(mpEditEngine, CreateEPaM(aPaM), rStr));
 
        aPaM = maEditDoc.InsertText( aPaM, rStr );
        TextModified();
    }
    else
    {
        aPaM = ImpInsertText( EditSelection(aPaM), rStr );
    }
 
    return aPaM;
}
 
EditPaM ImpEditEngine::ImpInsertFeature(const EditSelection& rCurSel, const SfxPoolItem& rItem)
{
    EditPaM aPaM;
    if ( rCurSel.HasRange() )
        aPaM = ImpDeleteSelection( rCurSel );
    else
        aPaM = rCurSel.Max();
 
    if ( aPaM.GetIndex() >= SAL_MAX_INT32-1 )
        return aPaM;
 
    if ( IsUndoEnabled() && !IsInUndo() )
        InsertUndo(std::make_unique<EditUndoInsertFeature>(mpEditEngine, CreateEPaM(aPaM), rItem));
    aPaM = maEditDoc.InsertFeature( aPaM, rItem );
    UpdateFields();
 
    ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() );
    assert(pPortion);
    pPortion->MarkInvalid( aPaM.GetIndex()-1, 1 );
 
    TextModified();
 
    return aPaM;
}
 
EditPaM ImpEditEngine::ImpInsertParaBreak( const EditSelection& rCurSel )
{
    EditPaM aPaM;
    if ( rCurSel.HasRange() )
        aPaM = ImpDeleteSelection( rCurSel );
    else
        aPaM = rCurSel.Max();
 
    return ImpInsertParaBreak( aPaM );
}
 
EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs )
{
    if ( maEditDoc.Count() >= EE_PARA_MAX_COUNT )
    {
        SAL_WARN( "editeng", "ImpEditEngine::ImpInsertParaBreak - can't process more than "
                << EE_PARA_MAX_COUNT << " paragraphs!");
        return rPaM;
    }
 
    if ( IsUndoEnabled() && !IsInUndo() )
        InsertUndo(std::make_unique<EditUndoSplitPara>(mpEditEngine, maEditDoc.GetPos(rPaM.GetNode()), rPaM.GetIndex()));
 
    EditPaM aPaM( maEditDoc.InsertParaBreak( rPaM, bKeepEndingAttribs ) );
    if (auto pStyle = aPaM.GetNode()->GetStyleSheet())
        StartListening(*pStyle, DuplicateHandling::Allow);
 
    if ( GetStatus().DoOnlineSpelling() )
    {
        sal_Int32 nEnd = rPaM.GetNode()->Len();
        aPaM.GetNode()->CreateWrongList();
        WrongList* pLWrongs = rPaM.GetNode()->GetWrongList();
        WrongList* pRWrongs = aPaM.GetNode()->GetWrongList();
        // take over misspelled words:
        for (auto & elem : *pLWrongs)
        {
            // Correct only if really a word gets overlapped in the process of
            // Spell checking
            if (elem.mnStart > o3tl::make_unsigned(nEnd))
            {
                pRWrongs->push_back(elem);
                editeng::MisspellRange& rRWrong = pRWrongs->back();
                rRWrong.mnStart = rRWrong.mnStart - nEnd;
                rRWrong.mnEnd = rRWrong.mnEnd - nEnd;
            }
            else if (elem.mnStart < o3tl::make_unsigned(nEnd) && elem.mnEnd > o3tl::make_unsigned(nEnd))
                elem.mnEnd = nEnd;
        }
        sal_Int32 nInv = nEnd ? nEnd-1 : nEnd;
        if ( nEnd )
            pLWrongs->SetInvalidRange(nInv, nEnd);
        else
            pLWrongs->SetValid();
        pRWrongs->SetValid();
        pRWrongs->SetInvalidRange(0, 1);  // Only test the first word
    }
 
    ParaPortion* pPortion = FindParaPortion( rPaM.GetNode() );
    assert(pPortion);
    pPortion->MarkInvalid( rPaM.GetIndex(), 0 );
 
    // Optimization: Do not place unnecessarily many getPos to Listen!
    // Here, as in undo, but also in all other methods.
    sal_Int32 nPos = GetParaPortions().GetPos( pPortion );
    assert(nPos != EE_PARA_NOT_FOUND);
    ParaPortion* pNewPortion = new ParaPortion( aPaM.GetNode() );
    GetParaPortions().Insert(nPos+1, std::unique_ptr<ParaPortion>(pNewPortion));
    ParaAttribsChanged( pNewPortion->GetNode() );
    if ( IsCallParaInsertedOrDeleted() )
        GetEditEnginePtr()->ParagraphInserted( nPos+1 );
 
    if( nullptr != rPaM.GetNode() )
        rPaM.GetNode()->checkAndDeleteEmptyAttribs(); // if empty Attributes have emerged.
 
    TextModified();
    return aPaM;
}
 
EditPaM ImpEditEngine::ImpFastInsertParagraph( sal_Int32 nPara )
{
    if ( IsUndoEnabled() && !IsInUndo() )
    {
        if ( nPara )
        {
            assert(maEditDoc.GetObject(nPara - 1));
            InsertUndo(std::make_unique<EditUndoSplitPara>(mpEditEngine, nPara-1, maEditDoc.GetObject(nPara - 1)->Len()));
        }
        else
            InsertUndo(std::make_unique<EditUndoSplitPara>(mpEditEngine, 0, 0));
    }
 
    ContentNode* pNode = new ContentNode( maEditDoc.GetItemPool() );
    // If flat mode, then later no Font is set:
    pNode->GetCharAttribs().GetDefFont() = maEditDoc.GetDefFont();
 
    if ( GetStatus().DoOnlineSpelling() )
        pNode->CreateWrongList();
 
    maEditDoc.Insert(nPara, std::unique_ptr<ContentNode>(pNode));
 
    GetParaPortions().Insert(nPara, std::make_unique<ParaPortion>( pNode ));
    if ( IsCallParaInsertedOrDeleted() )
        GetEditEnginePtr()->ParagraphInserted( nPara );
 
    return EditPaM( pNode, 0 );
}
 
EditPaM ImpEditEngine::InsertParaBreak(const EditSelection& rCurSel)
{
    EditPaM aPaM(ImpInsertParaBreak(rCurSel));
    if ( maStatus.DoAutoIndenting() )
    {
        sal_Int32 nPara = maEditDoc.GetPos( aPaM.GetNode() );
        OSL_ENSURE( nPara > 0, "AutoIndenting: Error!" );
        const OUString aPrevParaText( GetEditDoc().GetParaAsString( nPara-1 ) );
        sal_Int32 n = 0;
        while ( ( n < aPrevParaText.getLength() ) &&
                ( ( aPrevParaText[n] == ' ' ) || ( aPrevParaText[n] == '\t' ) ) )
        {
            if ( aPrevParaText[n] == '\t' )
                aPaM = ImpInsertFeature( EditSelection(aPaM), SfxVoidItem( EE_FEATURE_TAB ) );
            else
                aPaM = ImpInsertText( EditSelection(aPaM), OUString(aPrevParaText[n]) );
            n++;
        }
 
    }
    return aPaM;
}
 
EditPaM ImpEditEngine::InsertTab(const EditSelection& rCurSel)
{
    EditPaM aPaM( ImpInsertFeature(rCurSel, SfxVoidItem(EE_FEATURE_TAB )));
    return aPaM;
}
 
EditPaM ImpEditEngine::InsertField(const EditSelection& rCurSel, const SvxFieldItem& rFld)
{
    return ImpInsertFeature(rCurSel, rFld);
}
 
bool ImpEditEngine::UpdateFields()
{
    bool bChanges = false;
    sal_Int32 nParas = GetEditDoc().Count();
    for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ )
    {
        bool bChangesInPara = false;
        ContentNode* pNode = GetEditDoc().GetObject( nPara );
        assert(pNode);
        CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs();
        for (std::unique_ptr<EditCharAttrib> & rAttrib : rAttribs)
        {
            EditCharAttrib& rAttr = *rAttrib;
            if (rAttr.Which() == EE_FEATURE_FIELD)
            {
                EditCharAttribField& rField = static_cast<EditCharAttribField&>(rAttr);
                EditCharAttribField aCurrent(rField);
                rField.Reset();
 
                if (!maStatus.MarkNonUrlFields() && !maStatus.MarkUrlFields())
                    ;   // nothing marked
                else if (maStatus.MarkNonUrlFields() && maStatus.MarkUrlFields())
                    rField.GetFieldColor() = GetColorConfig().GetColorValue(svtools::WRITERFIELDSHADINGS).nColor;
                else
                {
                    bool bURL = false;
                    if (const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(rField.GetItem()))
                    {
                        if (const SvxFieldData* pFieldData = pFieldItem->GetField())
                            bURL = (dynamic_cast<const SvxURLField* >(pFieldData) != nullptr);
                    }
                    if ((bURL && maStatus.MarkUrlFields()) || (!bURL && maStatus.MarkNonUrlFields()))
                        rField.GetFieldColor() = GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor;
                }
 
                const OUString aFldValue =
                    GetEditEnginePtr()->CalcFieldValue(
                        static_cast<const SvxFieldItem&>(*rField.GetItem()),
                        nPara, rField.GetStart(), rField.GetTextColor(), rField.GetFieldColor(), rField.GetFldLineStyle() );
 
                rField.SetFieldValue(aFldValue);
                if (rField != aCurrent)
                {
                    bChanges = true;
                    bChangesInPara = true;
                }
            }
        }
        if ( bChangesInPara )
        {
            // If possible be more precise when invalidate.
            assert(GetParaPortions().exists(nPara));
            ParaPortion& rPortion = GetParaPortions().getRef(nPara);
            rPortion.MarkSelectionInvalid( 0 );
        }
    }
    return bChanges;
}
 
EditPaM ImpEditEngine::InsertLineBreak(const EditSelection& aCurSel)
{
    EditPaM aPaM( ImpInsertFeature( aCurSel, SfxVoidItem( EE_FEATURE_LINEBR ) ) );
    return aPaM;
}
 
 
//  Helper functions
 
tools::Rectangle ImpEditEngine::GetEditCursor(ParaPortion const& rPortion, EditLine const& rLine,
                                              sal_Int32 nIndex, CursorFlags aFlags)
{
    // nIndex might be not in the line
    // Search within the line...
    tools::Long nX;
 
    if (nIndex == rLine.GetStart() && aFlags.bStartOfLine)
    {
        Range aXRange = GetLineXPosStartEnd(rPortion, rLine);
        nX = !IsRightToLeft(GetEditDoc().GetPos(rPortion.GetNode())) ? aXRange.Min()
                                                                      : aXRange.Max();
    }
    else if (nIndex == rLine.GetEnd() && aFlags.bEndOfLine)
    {
        Range aXRange = GetLineXPosStartEnd(rPortion, rLine);
        nX = !IsRightToLeft(GetEditDoc().GetPos(rPortion.GetNode())) ? aXRange.Max()
                                                                      : aXRange.Min();
    }
    else
    {
        nX = GetXPos(rPortion, rLine, nIndex, aFlags.bPreferPortionStart);
    }
 
    tools::Rectangle aEditCursor;
    aEditCursor.SetLeft(nX);
    aEditCursor.SetRight(nX);
 
    aEditCursor.SetBottom(rLine.GetHeight() - 1);
    if (aFlags.bTextOnly)
        aEditCursor.SetTop(aEditCursor.Bottom() - rLine.GetTxtHeight() + 1);
    else
        aEditCursor.SetTop(aEditCursor.Bottom() - std::min(rLine.GetTxtHeight(), rLine.GetHeight()) + 1);
    return aEditCursor;
}
 
tools::Rectangle ImpEditEngine::PaMtoEditCursor( EditPaM aPaM, CursorFlags aFlags)
{
    assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: PaMtoEditCursor" );
 
    tools::Rectangle aEditCursor;
    const sal_Int32 nIndex = aPaM.GetIndex();
    const ParaPortion* pPortion = nullptr;
    const EditLine* pLastLine = nullptr;
    tools::Rectangle aLineArea;
 
    auto FindPortionLineAndArea = [&, bEOL(aFlags.bEndOfLine)](const LineAreaInfo& rInfo)
    {
        if (!rInfo.pLine) // start of ParaPortion
        {
            ContentNode* pNode = rInfo.rPortion.GetNode();
            OSL_ENSURE(pNode, "Invalid Node in Portion!");
            if (pNode != aPaM.GetNode())
                return CallbackResult::SkipThisPortion;
            pPortion = &rInfo.rPortion;
        }
        else // guaranteed that this is the correct ParaPortion
        {
            pLastLine = rInfo.pLine;
            aLineArea = rInfo.aArea;
            if ((rInfo.pLine->GetStart() == nIndex) || (rInfo.pLine->IsIn(nIndex, bEOL)))
                return CallbackResult::Stop;
        }
        return CallbackResult::Continue;
    };
    IterateLineAreas(FindPortionLineAndArea, IterFlag::none);
 
    if (pLastLine && pPortion)
    {
        aEditCursor = GetEditCursor(*pPortion, *pLastLine, nIndex, aFlags);
        aEditCursor.Move(getTopLeftDocOffset(aLineArea));
    }
    else
        OSL_FAIL("Line not found!");
 
    return aEditCursor;
}
 
void ImpEditEngine::IterateLineAreas(const IterateLinesAreasFunc& f, IterFlag eOptions)
{
    const Point aOrigin(0, 0);
    Point aLineStart(aOrigin);
    const tools::Long nVertLineSpacing = CalcVertLineSpacing(aLineStart);
    const tools::Long nColumnWidth = GetColumnWidth(maPaperSize);
    sal_Int16 nColumn = 0;
    for (sal_Int32 n = 0, nPortions = GetParaPortions().Count(); n < nPortions; ++n)
    {
        ParaPortion& rPortion = GetParaPortions().getRef(n);
        bool bSkipThis = true;
        if (rPortion.IsVisible())
        {
            // when typing idle formatting, asynchronous Paint. Invisible Portions may be invalid.
            if (rPortion.IsInvalid())
                return;
 
            LineAreaInfo aInfo{
                rPortion,
                nullptr, // pLine
                0, // nHeightNeededToNotWrap
                { aLineStart, Size{ nColumnWidth, rPortion.GetFirstLineOffset() } }, // aArea
                n, // nPortion
                0, // nLine
                nColumn // nColumn
            };
            auto eResult = f(aInfo);
            if (eResult == CallbackResult::Stop)
                return;
            bSkipThis = eResult == CallbackResult::SkipThisPortion;
 
            sal_uInt16 nSBL = 0;
            if (!maStatus.IsOutliner())
            {
                const SvxLineSpacingItem& rLSItem
                    = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL);
                nSBL = (rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix)
                           ? scaleYSpacingValue(rLSItem.GetInterLineSpace())
                           : 0;
            }
 
            adjustYDirectionAware(aLineStart, rPortion.GetFirstLineOffset());
            for (sal_Int32 nLine = 0, nLines = rPortion.GetLines().Count(); nLine < nLines; nLine++)
            {
                EditLine& rLine = rPortion.GetLines()[nLine];
                tools::Long nLineHeight = rLine.GetHeight();
                if (nLine != nLines - 1)
                    nLineHeight += nVertLineSpacing;
                MoveToNextLine(aLineStart, nLineHeight, nColumn, aOrigin,
                               &aInfo.nHeightNeededToNotWrap);
                const bool bInclILS = eOptions & IterFlag::inclILS;
                if (bInclILS && (nLine != nLines - 1) && !maStatus.IsOutliner())
                {
                    adjustYDirectionAware(aLineStart, nSBL);
                    nLineHeight += nSBL;
                }
 
                if (!bSkipThis)
                {
                    Point aOtherCorner(aLineStart);
                    adjustXDirectionAware(aOtherCorner, nColumnWidth);
                    adjustYDirectionAware(aOtherCorner, -nLineHeight);
 
                    // Calls to f() for each line
                    aInfo.nColumn = nColumn;
                    aInfo.pLine = &rLine;
                    aInfo.nLine = nLine;
                    aInfo.aArea = tools::Rectangle::Normalize(aLineStart, aOtherCorner);
                    eResult = f(aInfo);
                    if (eResult == CallbackResult::Stop)
                        return;
                    bSkipThis = eResult == CallbackResult::SkipThisPortion;
                }
 
                if (!bInclILS && (nLine != nLines - 1) && !maStatus.IsOutliner())
                    adjustYDirectionAware(aLineStart, nSBL);
            }
            if (!maStatus.IsOutliner())
            {
                const SvxULSpaceItem& rULItem = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE);
                tools::Long nUL = scaleYSpacingValue(rULItem.GetLower());
                adjustYDirectionAware(aLineStart, nUL);
            }
        }
        // Invisible ParaPortion has no height (see ParaPortion::GetHeight), don't handle it
    }
}
 
std::tuple<const ParaPortion*, const EditLine*, tools::Long>
ImpEditEngine::GetPortionAndLine(Point aDocPos)
{
    // First find the column from the point
    sal_Int32 nClickColumn = 0;
    for (tools::Long nColumnStart = 0, nColumnWidth = GetColumnWidth(maPaperSize);;
         nColumnStart += mnColumnSpacing + nColumnWidth, ++nClickColumn)
    {
        if (aDocPos.X() <= nColumnStart + nColumnWidth + mnColumnSpacing / 2)
            break;
        if (nClickColumn >= mnColumns - 1)
            break;
    }
 
    const ParaPortion* pLastPortion = nullptr;
    const EditLine* pLastLine = nullptr;
    tools::Long nLineStartX = 0;
    Point aPos;
    adjustYDirectionAware(aPos, aDocPos.Y());
 
    auto FindLastMatchingPortionAndLine = [&](const LineAreaInfo& rInfo) {
        if (rInfo.pLine) // Only handle lines, not ParaPortion starts
        {
            if (rInfo.nColumn > nClickColumn)
                return CallbackResult::Stop;
            pLastPortion = &rInfo.rPortion; // Candidate paragraph
            pLastLine = rInfo.pLine; // Last visible line not later than click position
            nLineStartX = getTopLeftDocOffset(rInfo.aArea).Width();
            if (rInfo.nColumn == nClickColumn && getYOverflowDirectionAware(aPos, rInfo.aArea) == 0)
                return CallbackResult::Stop; // Found it
        }
        return CallbackResult::Continue;
    };
    IterateLineAreas(FindLastMatchingPortionAndLine, IterFlag::inclILS);
 
    return { pLastPortion, pLastLine, nLineStartX };
}
 
EditPaM ImpEditEngine::GetPaM( Point aDocPos, bool bSmart )
{
    assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: GetPaM" );
 
    if (const auto [pPortion, pLine, nLineStartX] = GetPortionAndLine(aDocPos); pPortion)
    {
        assert(pLine);
        assert(pPortion);
        sal_Int32 nCurIndex = GetChar(*pPortion, *pLine, aDocPos.X() - nLineStartX, bSmart);
        EditPaM aPaM(pPortion->GetNode(), nCurIndex);
 
        if (nCurIndex && (nCurIndex == pLine->GetEnd())
            && (pLine != &pPortion->GetLines()[pPortion->GetLines().Count() - 1]))
        {
            aPaM = CursorLeft(aPaM);
        }
 
        return aPaM;
    }
    return {};
}
 
bool ImpEditEngine::IsTextPos(const Point& rDocPos, sal_uInt16 nBorder)
{
    if (const auto [pPortion, pLine, nLineStartX] = GetPortionAndLine(rDocPos); pPortion)
    {
        assert(pLine);
        assert(pPortion);
        Range aLineXPosStartEnd = GetLineXPosStartEnd(*pPortion, *pLine);
        if ((rDocPos.X() >= nLineStartX + aLineXPosStartEnd.Min() - nBorder)
            && (rDocPos.X() <= nLineStartX + aLineXPosStartEnd.Max() + nBorder))
            return true;
    }
    return false;
}
 
sal_uInt32 ImpEditEngine::GetTextHeight() const
{
    assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" );
    OSL_ENSURE( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" );
    return mnCurTextHeight;
}
 
sal_uInt32 ImpEditEngine::CalcTextWidth( bool bIgnoreExtraSpace )
{
    // If still not formatted and not in the process.
    // Will be brought in the formatting for AutoPageSize.
    if ( !IsFormatted() && !IsFormatting() )
        FormatDoc();
 
    sal_uInt32 nMaxWidth = 0;
 
    // Over all the paragraphs ...
 
    sal_Int32 nParas = GetParaPortions().Count();
    for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ )
    {
        nMaxWidth = std::max(nMaxWidth, CalcParaWidth(nPara, bIgnoreExtraSpace));
    }
 
    return nMaxWidth;
}
 
sal_uInt32 ImpEditEngine::CalcParaWidth( sal_Int32 nPara, bool bIgnoreExtraSpace )
{
    // If still not formatted and not in the process.
    // Will be brought in the formatting for AutoPageSize.
    if ( !IsFormatted() && !IsFormatting() )
        FormatDoc();
 
    tools::Long nMaxWidth = 0;
 
    // Over all the paragraphs ...
 
    OSL_ENSURE(GetParaPortions().exists(nPara), "CalcParaWidth: Out of range");
    ParaPortion* pPortion = GetParaPortions().SafeGetObject(nPara);
    if ( pPortion && pPortion->IsVisible() )
    {
        const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pPortion->GetNode() );
        sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pPortion->GetNode() );
 
 
        // On the lines of the paragraph ...
 
        sal_Int32 nLines = pPortion->GetLines().Count();
        for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ )
        {
            EditLine const& rLine = pPortion->GetLines()[nLine];
            // nCurWidth = pLine->GetStartPosX();
            // For Center- or Right- alignment it depends on the paper
            // width, here not preferred. I general, it is best not leave it
            // to StartPosX, also the right indents have to be taken into
            // account!
            tools::Long nCurWidth = scaleXSpacingValue(rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth);
            if ( nLine == 0 )
            {
                tools::Long nFI = scaleXSpacingValue(rLRItem.GetTextFirstLineOffset());
                nCurWidth -= nFI;
                if ( pPortion->GetBulletX() > nCurWidth )
                {
                    nCurWidth += nFI;   // LI?
                    if ( pPortion->GetBulletX() > nCurWidth )
                        nCurWidth = pPortion->GetBulletX();
                }
            }
            nCurWidth += scaleXSpacingValue(rLRItem.GetRight());
            nCurWidth += CalcLineWidth(*pPortion, rLine, bIgnoreExtraSpace);
            if ( nCurWidth > nMaxWidth )
            {
                nMaxWidth = nCurWidth;
            }
        }
    }
 
    nMaxWidth++; // widen it, because in CreateLines for >= is wrapped.
    return static_cast<sal_uInt32>(nMaxWidth);
}
 
sal_uInt32 ImpEditEngine::CalcLineWidth(ParaPortion const& rPortion, EditLine const& rLine, bool bIgnoreExtraSpace)
{
    sal_Int32 nPara = GetEditDoc().GetPos(rPortion.GetNode());
 
    // #114278# Saving both layout mode and language (since I'm
    // potentially changing both)
    GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE );
 
    ImplInitLayoutMode(*GetRefDevice(), nPara, -1);
 
    SvxAdjust eJustification = GetJustification( nPara );
 
    // Calculation of the width without the Indents ...
    sal_uInt32 nWidth = 0;
    sal_Int32 nPos = rLine.GetStart();
    for ( sal_Int32 nTP = rLine.GetStartPortion(); nTP <= rLine.GetEndPortion(); nTP++ )
    {
        const TextPortion& rTextPortion = rPortion.GetTextPortions()[nTP];
        switch ( rTextPortion.GetKind() )
        {
            case PortionKind::FIELD:
            case PortionKind::HYPHENATOR:
            case PortionKind::TAB:
            {
                nWidth += rTextPortion.GetSize().Width();
            }
            break;
            case PortionKind::TEXT:
            {
                if ( ( eJustification != SvxAdjust::Block ) || ( !bIgnoreExtraSpace ) )
                {
                    nWidth += rTextPortion.GetSize().Width();
                }
                else
                {
                    SvxFont aTmpFont(rPortion.GetNode()->GetCharAttribs().GetDefFont());
                    SeekCursor(rPortion.GetNode(), nPos + 1, aTmpFont);
                    aTmpFont.SetPhysFont(*GetRefDevice());
                    ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
                    nWidth += aTmpFont.QuickGetTextSize( GetRefDevice(),
                        rPortion.GetNode()->GetString(), nPos, rTextPortion.GetLen(), nullptr ).Width();
                }
            }
            break;
            case PortionKind::LINEBREAK: break;
        }
        nPos = nPos + rTextPortion.GetLen();
    }
 
    GetRefDevice()->Pop();
 
    return nWidth;
}
 
tools::Long ImpEditEngine::Calc1ColumnTextHeight()
{
    tools::Long nHeight = 0;
    // Pretend that we have ~infinite height to get total height
    comphelper::ValueRestorationGuard aGuard(mnCurTextHeight, std::numeric_limits<tools::Long>::max());
 
    IterateLinesAreasFunc FindLastLineBottom = [&](const LineAreaInfo& rInfo) {
        if (rInfo.pLine)
        {
            // bottom coordinate does not belong to area, so no need to do +1
            nHeight = getBottomDocOffset(rInfo.aArea);
        }
        return CallbackResult::Continue;
    };
    IterateLineAreas(FindLastLineBottom, IterFlag::none);
    return nHeight;
}
 
tools::Long ImpEditEngine::CalcTextHeight()
{
    assert( IsUpdateLayout() && "Should not be used when Update=FALSE: CalcTextHeight" );
 
    if (mnColumns <= 1)
        return Calc1ColumnTextHeight(); // All text fits into a single column - done!
 
    // The final column height can be smaller than total height divided by number of columns (taking
    // into account first line offset and interline spacing, that aren't considered in positioning
    // after the wrap). The wrap should only happen after the minimal height is exceeded.
    tools::Long nTentativeColHeight = mnMinColumnWrapHeight;
    tools::Long nWantedIncrease = 0;
    tools::Long nCurrentTextHeight;
 
    // This does the necessary column balancing for the case when the text does not fit min height.
    // When the height of column (taken from mnCurTextHeight) is too small, the last column will
    // overflow, so the resulting height of the text will exceed the set column height. Increasing
    // the column height step by step by the minimal value that allows one of columns to accommodate
    // one line more, we finally get to the point where all the text fits. At each iteration, the
    // height is only increased, so it's impossible to have infinite layout loops. The found value
    // is the global minimum.
    //
    // E.g., given the following four line heights:
    // Line 1: 10;
    // Line 2: 12;
    // Line 3: 10;
    // Line 4: 10;
    // number of columns 3, and the minimal paper height of 5, the iterations would be:
    // * Tentative column height is set to 5
    // <ITERATION 1>
    // * Line 1 is attempted to go to column 0. Overflow is 5 => moved to column 1.
    // * Line 2 is attempted to go to column 1 after Line 1; overflow is 17 => moved to column 2.
    // * Line 3 is attempted to go to column 2 after Line 2; overflow is 17, stays in max column 2.
    // * Line 4 goes to column 2 after Line 3.
    // * Final iteration columns are: {empty}, {Line 1}, {Line 2, Line 3, Line 4}
    // * Total text height is max({0, 10, 32}) == 32 > Tentative column height 5 => NEXT ITERATION
    // * Minimal height increase that allows at least one column to accommodate one more line is
    //   min({5, 17, 17}) = 5.
    // * Tentative column height is set to 5 + 5 = 10.
    // <ITERATION 2>
    // * Line 1 goes to column 0, no overflow.
    // * Line 2 is attempted to go to column 0 after Line 1; overflow is 12 => moved to column 1.
    // * Line 3 is attempted to go to column 1 after Line 2; overflow is 12 => moved to column 2.
    // * Line 4 is attempted to go to column 2 after Line 3; overflow is 10, stays in max column 2.
    // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4}
    // * Total text height is max({10, 12, 20}) == 20 > Tentative column height 10 => NEXT ITERATION
    // * Minimal height increase that allows at least one column to accommodate one more line is
    //   min({12, 12, 10}) = 10.
    // * Tentative column height is set to 10 + 10 == 20.
    // <ITERATION 3>
    // * Line 1 goes to column 0, no overflow.
    // * Line 2 is attempted to go to column 0 after Line 1; overflow is 2 => moved to column 1.
    // * Line 3 is attempted to go to column 1 after Line 2; overflow is 2 => moved to column 2.
    // * Line 4 is attempted to go to column 2 after Line 3; no overflow.
    // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4}
    // * Total text height is max({10, 12, 20}) == 20 == Tentative column height 20 => END.
    do
    {
        nTentativeColHeight += nWantedIncrease;
        nWantedIncrease = std::numeric_limits<tools::Long>::max();
        nCurrentTextHeight = 0;
        auto GetHeightAndWantedIncrease = [&, minHeight = tools::Long(0), lastCol = sal_Int16(0)](
                                              const LineAreaInfo& rInfo) mutable {
            if (rInfo.pLine)
            {
                if (lastCol != rInfo.nColumn)
                {
                    minHeight = std::max(nCurrentTextHeight,
                                    minHeight); // total height can't be less than previous columns
                    nWantedIncrease = std::min(rInfo.nHeightNeededToNotWrap, nWantedIncrease);
                    lastCol = rInfo.nColumn;
                }
                // bottom coordinate does not belong to area, so no need to do +1
                nCurrentTextHeight = std::max(getBottomDocOffset(rInfo.aArea), minHeight);
            }
            return CallbackResult::Continue;
        };
        comphelper::ValueRestorationGuard aGuard(mnCurTextHeight, nTentativeColHeight);
        IterateLineAreas(GetHeightAndWantedIncrease, IterFlag::none);
    } while (nCurrentTextHeight > nTentativeColHeight && nWantedIncrease > 0
             && nWantedIncrease != std::numeric_limits<tools::Long>::max());
    return nCurrentTextHeight;
}
 
sal_Int32 ImpEditEngine::GetLineCount( sal_Int32 nParagraph )
{
    if (!IsFormatted())
        FormatDoc();
    OSL_ENSURE(GetParaPortions().exists(nParagraph), "GetLineCount: Out of range");
    const ParaPortion* pPPortion = GetParaPortions().SafeGetObject(nParagraph);
    OSL_ENSURE( pPPortion, "Paragraph not found: GetLineCount" );
    if ( pPPortion )
        return pPPortion->GetLines().Count();
 
    return -1;
}
 
sal_Int32 ImpEditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine )
{
    if (!IsFormatted())
        FormatDoc();
    OSL_ENSURE(GetParaPortions().exists(nParagraph), "GetLineLen: Out of range");
    const ParaPortion* pPPortion = GetParaPortions().SafeGetObject(nParagraph);
    OSL_ENSURE(pPPortion, "Paragraph not found: GetLineLen");
    if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) )
    {
        const EditLine& rLine = pPPortion->GetLines()[nLine];
        return rLine.GetLen();
    }
 
    return -1;
}
 
void ImpEditEngine::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nParagraph, sal_Int32 nLine )
{
    if (!IsFormatted())
        FormatDoc();
    OSL_ENSURE(GetParaPortions().exists(nParagraph), "GetLineCount: Out of range");
    const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
    OSL_ENSURE( pPPortion, "Paragraph not found: GetLineBoundaries" );
    rStart = rEnd = -1;     // default values in case of error
    if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) )
    {
        const EditLine& rLine = pPPortion->GetLines()[nLine];
        rStart = rLine.GetStart();
        rEnd   = rLine.GetEnd();
    }
}
 
sal_Int32 ImpEditEngine::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex )
{
    if (!IsFormatted())
        FormatDoc();
    const ContentNode* pNode = GetEditDoc().GetObject( nPara );
    OSL_ENSURE( pNode, "GetLineNumberAtIndex: invalid paragraph index" );
    if (!pNode)
        return -1;
    // we explicitly allow for the index to point at the character right behind the text
    const bool bValidIndex = /*0 <= nIndex &&*/ nIndex <= pNode->Len();
    OSL_ENSURE( bValidIndex, "GetLineNumberAtIndex: invalid index" );
    const ParaPortion* pPPortion = maParaPortionList.SafeGetObject(nPara);
    if (!pPPortion)
    {
        SAL_WARN( "editeng", "ImpEditEngine::GetLineNumberAtIndex missing ParaPortion");
        return -1;
    }
    const EditLineList& rLineList = pPPortion->GetLines();
    const sal_Int32 nLineCount = rLineList.Count();
    sal_Int32 nLineNo = -1;
    if (nIndex == pNode->Len())
        nLineNo = nLineCount > 0 ? nLineCount - 1 : 0;
    else if (bValidIndex)   // nIndex < pNode->Len()
    {
        sal_Int32 nStart = -1, nEnd = -1;
        for (sal_Int32 i = 0;  i < nLineCount && nLineNo == -1;  ++i)
        {
            const EditLine& rLine = rLineList[i];
            nStart = rLine.GetStart();
            nEnd   = rLine.GetEnd();
            if (nStart >= 0 && nStart <= nIndex && nEnd >= 0 && nIndex < nEnd)
                nLineNo = i;
        }
    }
    return nLineNo;
}
 
sal_uInt16 ImpEditEngine::GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine )
{
    if (!IsFormatted())
        FormatDoc();
    OSL_ENSURE(GetParaPortions().exists(nParagraph), "GetLineCount: Out of range");
    ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
    OSL_ENSURE( pPPortion, "Paragraph not found: GetLineHeight" );
    if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) )
    {
        const EditLine& rLine = pPPortion->GetLines()[nLine];
        return rLine.GetHeight();
    }
 
    return 0xFFFF;
}
 
tools::Rectangle ImpEditEngine::GetParaBounds( sal_Int32 nPara )
{
    if (!IsFormatted())
        FormatDoc();
    Point aPnt = GetDocPosTopLeft( nPara );
 
    if( IsEffectivelyVertical() )
    {
        sal_Int32 nTextHeight = GetTextHeight();
        sal_Int32 nParaWidth = CalcParaWidth(nPara, true);
        sal_Int32 nParaHeight = GetParaHeight(nPara);
 
        return tools::Rectangle( nTextHeight - aPnt.Y() - nParaHeight, 0, nTextHeight - aPnt.Y(), nParaWidth );
    }
    else
    {
        sal_Int32 nParaWidth = CalcParaWidth( nPara, true );
        sal_Int32 nParaHeight = GetParaHeight( nPara );
 
        return tools::Rectangle( 0, aPnt.Y(), nParaWidth, aPnt.Y() + nParaHeight );
    }
}
 
Point ImpEditEngine::GetDocPosTopLeft( sal_Int32 nParagraph )
{
    const ParaPortion* pPPortion = maParaPortionList.SafeGetObject(nParagraph);
    DBG_ASSERT( pPPortion, "Paragraph not found: GetWindowPosTopLeft" );
    Point aPoint;
    if ( pPPortion )
    {
        // If someone calls GetLineHeight() with an empty Engine.
        DBG_ASSERT(IsFormatted() || !IsFormatting(), "GetDocPosTopLeft: Doc not formatted - unable to format!");
        if (!IsFormatted())
            FormatAndLayout();
        if (pPPortion->GetLines().Count())
        {
            // Correct it if large Bullet.
            const EditLine& rFirstLine = pPPortion->GetLines()[0];
            aPoint.setX( rFirstLine.GetStartPosX() );
        }
        else
        {
            const SvxLRSpaceItem& rLRItem = GetLRSpaceItem(pPPortion->GetNode());
            sal_Int32 nSpaceBefore = 0;
            GetSpaceBeforeAndMinLabelWidth(pPPortion->GetNode(), &nSpaceBefore);
            short nX = static_cast<short>(rLRItem.GetTextLeft()
                            + rLRItem.GetTextFirstLineOffset()
                            + nSpaceBefore);
 
            aPoint.setX(scaleXSpacingValue(nX));
        }
        aPoint.setY(maParaPortionList.GetYOffset(pPPortion));
    }
    return aPoint;
}
 
sal_uInt32 ImpEditEngine::GetParaHeight(sal_Int32 nParagraph) const
{
    sal_uInt32 nHeight = 0;
 
    ParaPortion const* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
    OSL_ENSURE( pPPortion, "Paragraph not found: GetParaHeight" );
 
    if ( pPPortion )
        nHeight = pPPortion->GetHeight();
 
    return nHeight;
}
 
void ImpEditEngine::UpdateSelections()
{
    // Check whether one of the selections is at a deleted node...
    // If the node is valid, the index has yet to be examined!
    for (EditView* pView : maEditViews)
    {
        EditSelection aCurSel( pView->getImpl().GetEditSelection() );
        bool bChanged = false;
        for (const std::unique_ptr<DeletedNodeInfo> & aDeletedNode : maDeletedNodes)
        {
            const DeletedNodeInfo& rInf = *aDeletedNode;
            if ( ( aCurSel.Min().GetNode() == rInf.GetNode() ) ||
                 ( aCurSel.Max().GetNode() == rInf.GetNode() ) )
            {
                // Use ParaPortions, as now also hidden paragraphs have to be
                // taken into account!
                sal_Int32 nPara = rInf.GetPosition();
                if (!GetParaPortions().exists(nPara)) // Last paragraph
                {
                    nPara = GetParaPortions().lastIndex();
                }
                assert(GetParaPortions().exists(nPara) && "Empty Document in UpdateSelections ?");
                // Do not end up from a hidden paragraph:
                sal_Int32 nCurrentPara = nPara;
                sal_Int32 nLastParaIndex = GetParaPortions().lastIndex();
                while (nPara <= nLastParaIndex && !GetParaPortions().getRef(nPara).IsVisible())
                    nPara++;
                if (nPara > nLastParaIndex) // then also backwards ...
                {
                    nPara = nCurrentPara;
                    while ( nPara && !GetParaPortions().getRef(nPara).IsVisible() )
                        nPara--;
                }
                OSL_ENSURE(GetParaPortions().getRef(nPara).IsVisible(), "No visible paragraph found: UpdateSelections" );
 
                ParaPortion& rParaPortion = GetParaPortions().getRef(nPara);
                EditSelection aTmpSelection(EditPaM(rParaPortion.GetNode(), 0));
                pView->getImpl().SetEditSelection( aTmpSelection );
                bChanged=true;
                break;  // for loop
            }
        }
        if ( !bChanged )
        {
            // Check Index if node shrunk.
            if ( aCurSel.Min().GetIndex() > aCurSel.Min().GetNode()->Len() )
            {
                aCurSel.Min().SetIndex( aCurSel.Min().GetNode()->Len() );
                pView->getImpl().SetEditSelection( aCurSel );
            }
            if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() )
            {
                aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() );
                pView->getImpl().SetEditSelection( aCurSel );
            }
        }
    }
    maDeletedNodes.clear();
}
 
EditSelection ImpEditEngine::ConvertSelection(
    sal_Int32 nStartPara, sal_Int32 nStartPos, sal_Int32 nEndPara, sal_Int32 nEndPos )
{
    EditSelection aNewSelection;
 
    // Start...
    ContentNode* pNode = maEditDoc.GetObject( nStartPara );
    sal_Int32 nIndex = nStartPos;
    if ( !pNode )
    {
        pNode = maEditDoc.GetObject(maEditDoc.Count() - 1);
        nIndex = pNode->Len();
    }
    else if ( nIndex > pNode->Len() )
        nIndex = pNode->Len();
 
    aNewSelection.Min().SetNode( pNode );
    aNewSelection.Min().SetIndex( nIndex );
 
    // End...
    pNode = maEditDoc.GetObject( nEndPara );
    nIndex = nEndPos;
    if ( !pNode )
    {
        pNode = maEditDoc.GetObject(maEditDoc.Count() - 1);
        nIndex = pNode->Len();
    }
    else if ( nIndex > pNode->Len() )
        nIndex = pNode->Len();
 
    aNewSelection.Max().SetNode( pNode );
    aNewSelection.Max().SetIndex( nIndex );
 
    return aNewSelection;
}
 
void ImpEditEngine::SetActiveView( EditView* pView )
{
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // Actually, now bHasVisSel and HideSelection would be necessary     !!!
 
    if (pView == mpActiveView)
        return;
 
    if (mpActiveView && mpActiveView->HasSelection())
        mpActiveView->getImpl().DrawSelectionXOR();
 
    mpActiveView = pView;
 
    if (mpActiveView && mpActiveView->HasSelection())
        mpActiveView->getImpl().DrawSelectionXOR();
 
    //  NN: Quick fix for #78668#:
    //  When editing of a cell in Calc is ended, the edit engine is not deleted,
    //  only the edit views are removed. If mpIMEInfos is still set in that case,
    //  mpIMEInfos->aPos points to an invalid selection.
    //  -> reset mpIMEInfos now
    //  (probably something like this is necessary whenever the content is modified
    //  from the outside)
 
    if ( !pView && mpIMEInfos )
    {
        mpIMEInfos.reset();
    }
}
 
uno::Reference< datatransfer::XTransferable > ImpEditEngine::CreateTransferable( const EditSelection& rSelection )
{
    EditSelection aSelection( rSelection );
    aSelection.Adjust( GetEditDoc() );
 
    rtl::Reference<EditDataObject> pDataObj = new EditDataObject;
 
    pDataObj->GetString() = convertLineEnd(GetSelected(aSelection), GetSystemLineEnd()); // System specific
 
    WriteRTF( pDataObj->GetRTFStream(), aSelection, /*bClipboard=*/true );
    pDataObj->GetRTFStream().Seek( 0 );
 
    WriteXML( pDataObj->GetODFStream(), aSelection );
    pDataObj->GetODFStream().Seek( 0 );
 
    //Dumping the ODFStream to a XML file for testing purpose
    /*
    std::filebuf afilebuf;
    afilebuf.open ("gsoc17_clipboard_test.xml",std::ios::out);
    std::ostream os(&afilebuf);
    os.write((const char*)(pDataObj->GetODFStream().GetData()), pDataObj->GetODFStream().remainingSize());
    afilebuf.close();
    */
    //dumping ends
 
    if ( ( aSelection.Min().GetNode() == aSelection.Max().GetNode() )
            && ( aSelection.Max().GetIndex() == (aSelection.Min().GetIndex()+1) ) )
    {
        const EditCharAttrib* pAttr = aSelection.Min().GetNode()->GetCharAttribs().
            FindFeature( aSelection.Min().GetIndex() );
        if ( pAttr &&
            ( pAttr->GetStart() == aSelection.Min().GetIndex() ) &&
            ( pAttr->Which() == EE_FEATURE_FIELD ) )
        {
            const SvxFieldItem* pField = static_cast<const SvxFieldItem*>(pAttr->GetItem());
            const SvxFieldData* pFld = pField->GetField();
            if ( auto pUrlField = dynamic_cast<const SvxURLField* >(pFld) )
            {
                // Office-Bookmark
                pDataObj->GetURL() = pUrlField->GetURL();
            }
        }
    }
 
    return pDataObj;
}
 
EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial, SotClipboardFormatId format)
{
    EditSelection aNewSelection( rPaM );
 
    if ( !rxDataObj.is() )
        return aNewSelection;
 
    datatransfer::DataFlavor aFlavor;
    bool bDone = false;
 
    if ( bUseSpecial )
    {
        // XML
        SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT, aFlavor );
        if ( rxDataObj->isDataFlavorSupported( aFlavor ) && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT == format))
        {
            try
            {
                uno::Any aData = rxDataObj->getTransferData( aFlavor );
                uno::Sequence< sal_Int8 > aSeq;
                aData >>= aSeq;
                {
                    SvMemoryStream aODFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ );
                    aNewSelection = Read( aODFStream, rBaseURL, EETextFormat::Xml, EditSelection(rPaM) );
                }
                bDone = true;
            }
            catch( const css::uno::Exception&)
            {
                TOOLS_WARN_EXCEPTION( "editeng", "Unable to paste EDITENGINE_ODF_TEXT_FLAT" );
            }
        }
 
        if (!bDone) {
            // HTML_SIMPLE
            SotExchange::GetFormatDataFlavor(SotClipboardFormatId::HTML_SIMPLE, aFlavor);
            bool bHtmlSupported = rxDataObj->isDataFlavorSupported(aFlavor);
            if (bHtmlSupported && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::HTML_SIMPLE == format)) {
                MSE40HTMLClipFormatObj aMSE40HTMLClipFormatObj;
                try
                {
                    uno::Any aData = rxDataObj->getTransferData(aFlavor);
                    uno::Sequence< sal_Int8 > aSeq;
                    aData >>= aSeq;
                    {
                        SvMemoryStream aHtmlStream(aSeq.getArray(), aSeq.getLength(), StreamMode::READ);
                        SvStream* pHtmlStream = aMSE40HTMLClipFormatObj.IsValid(aHtmlStream);
                        if (pHtmlStream != nullptr) {
                            aNewSelection = Read(*pHtmlStream, rBaseURL, EETextFormat::Html, EditSelection(rPaM));
                        }
                    }
                    bDone = true;
                }
                catch (const css::uno::Exception&)
                {
                }
            }
        }
 
        if ( !bDone )
        {
            // RTF
            SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RTF, aFlavor );
            // RICHTEXT
            datatransfer::DataFlavor aFlavorRichtext;
            SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RICHTEXT, aFlavorRichtext );
            bool bRtfSupported = rxDataObj->isDataFlavorSupported( aFlavor );
            bool bRichtextSupported  = rxDataObj->isDataFlavorSupported( aFlavorRichtext );
            if ( (bRtfSupported || bRichtextSupported) && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::RICHTEXT == format || SotClipboardFormatId::RTF == format))
            {
                if(bRichtextSupported)
                {
                    aFlavor = std::move(aFlavorRichtext);
                }
                try
                {
                    uno::Any aData = rxDataObj->getTransferData( aFlavor );
                    uno::Sequence< sal_Int8 > aSeq;
                    aData >>= aSeq;
                    {
                        SvMemoryStream aRTFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ );
                        aNewSelection = Read( aRTFStream, rBaseURL, EETextFormat::Rtf, EditSelection(rPaM) );
                    }
                    bDone = true;
                }
                catch( const css::uno::Exception& )
                {
                }
            }
        }
 
        if (!bDone)
        {
            // HTML
            SotExchange::GetFormatDataFlavor(SotClipboardFormatId::HTML, aFlavor);
            bool bHtmlSupported = rxDataObj->isDataFlavorSupported(aFlavor);
            if (bHtmlSupported
                && (format == SotClipboardFormatId::NONE || format == SotClipboardFormatId::HTML))
            {
                try
                {
                    uno::Any aData = rxDataObj->getTransferData(aFlavor);
                    uno::Sequence<sal_Int8> aSeq;
                    aData >>= aSeq;
                    SvMemoryStream aHtmlStream(aSeq.getArray(), aSeq.getLength(), StreamMode::READ);
                    aNewSelection = Read(aHtmlStream, rBaseURL, EETextFormat::Html, EditSelection(rPaM));
                    bDone = true;
                }
                catch (const css::uno::Exception&)
                {
                    TOOLS_WARN_EXCEPTION("editeng", "HTML paste failed");
                }
            }
        }
    }
    if ( !bDone )
    {
        SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
        if ( rxDataObj->isDataFlavorSupported( aFlavor ) )
        {
            try
            {
                uno::Any aData = rxDataObj->getTransferData( aFlavor );
                OUString aText;
                aData >>= aText;
                aNewSelection = ImpInsertText( EditSelection(rPaM), aText );
            }
            catch( ... )
            {
                ; // #i9286# can happen, even if isDataFlavorSupported returns true...
            }
        }
    }
 
    return aNewSelection;
}
 
sal_Int32 ImpEditEngine::GetChar(ParaPortion const& rParaPortion, EditLine const& rLine, tools::Long nXPos, bool bSmart)
{
    sal_Int32 nChar = -1;
    sal_Int32 nCurIndex = rLine.GetStart();
 
 
    // Search best matching portion with GetPortionXOffset()
    for ( sal_Int32 i = rLine.GetStartPortion(); i <= rLine.GetEndPortion(); i++ )
    {
        const TextPortion& rPortion = rParaPortion.GetTextPortions()[i];
        tools::Long nXLeft = GetPortionXOffset(rParaPortion, rLine, i);
        tools::Long nXRight = nXLeft + rPortion.GetSize().Width();
        if ( ( nXLeft <= nXPos ) && ( nXRight >= nXPos ) )
        {
            nChar = nCurIndex;
 
            // Search within Portion...
 
            // Don't search within special portions...
            if ( rPortion.GetKind() != PortionKind::TEXT )
            {
                // ...but check on which side
                if ( bSmart )
                {
                    tools::Long nLeftDiff = nXPos-nXLeft;
                    tools::Long nRightDiff = nXRight-nXPos;
                    if ( nRightDiff < nLeftDiff )
                        nChar++;
                }
            }
            else
            {
                sal_Int32 nMax = rPortion.GetLen();
                sal_Int32 nOffset = -1;
                sal_Int32 nTmpCurIndex = nChar - rLine.GetStart();
 
                tools::Long nXInPortion = nXPos - nXLeft;
                if ( rPortion.IsRightToLeft() )
                    nXInPortion = nXRight - nXPos;
 
                // Search in Array...
                for ( sal_Int32 x = 0; x < nMax; x++ )
                {
                    tools::Long nTmpPosMax = rLine.GetCharPosArray()[nTmpCurIndex+x];
                    if ( nTmpPosMax > nXInPortion )
                    {
                        // Check whether this or the previous...
                        tools::Long nTmpPosMin = x ? rLine.GetCharPosArray()[nTmpCurIndex+x-1] : 0;
                        tools::Long nDiffLeft = nXInPortion - nTmpPosMin;
                        tools::Long nDiffRight = nTmpPosMax - nXInPortion;
                        OSL_ENSURE( nDiffLeft >= 0, "DiffLeft negative" );
                        OSL_ENSURE( nDiffRight >= 0, "DiffRight negative" );
 
                        if (bSmart && nDiffRight < nDiffLeft)
                        {
                            // I18N: If there are character position with the length of 0,
                            // they belong to the same character, we can not use this position as an index.
                            // Skip all 0-positions, cheaper than using XBreakIterator:
                            tools::Long nX = rLine.GetCharPosArray()[nTmpCurIndex + x];
                            while(x < nMax && rLine.GetCharPosArray()[nTmpCurIndex + x] == nX)
                                ++x;
                        }
                        nOffset = x;
                        break;
                    }
                }
 
                // There should not be any inaccuracies when using the
                // CharPosArray! Maybe for kerning?
                // 0xFFF happens for example for Outline-Font when at the very end.
                if ( nOffset < 0 )
                    nOffset = nMax;
 
                OSL_ENSURE( nOffset <= nMax, "nOffset > nMax" );
 
                nChar = nChar + nOffset;
 
                // Check if index is within a cell:
                if ( nChar && ( nChar < rParaPortion.GetNode()->Len() ) )
                {
                    EditPaM aPaM( rParaPortion.GetNode(), nChar+1 );
                    sal_uInt16 nScriptType = GetI18NScriptType( aPaM );
                    if ( nScriptType == i18n::ScriptType::COMPLEX )
                    {
                        uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
                        sal_Int32 nCount = 1;
                        lang::Locale aLocale = GetLocale( aPaM );
                        sal_Int32 nRight = _xBI->nextCharacters(
                            rParaPortion.GetNode()->GetString(), nChar, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
                        sal_Int32 nLeft = _xBI->previousCharacters(
                            rParaPortion.GetNode()->GetString(), nRight, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
                        if ( ( nLeft != nChar ) && ( nRight != nChar ) )
                        {
                            nChar = ( std::abs( nRight - nChar ) < std::abs( nLeft - nChar ) ) ? nRight : nLeft;
                        }
                    }
                    else
                    {
                        OUString aStr(rParaPortion.GetNode()->GetString());
                        // tdf#102625: don't select middle of a pair of surrogates with mouse cursor
                        if (rtl::isSurrogate(aStr[nChar]))
                            --nChar;
                    }
                }
            }
        }
 
        nCurIndex = nCurIndex + rPortion.GetLen();
    }
 
    if ( nChar == -1 )
    {
        nChar = ( nXPos <= rLine.GetStartPosX() ) ? rLine.GetStart() : rLine.GetEnd();
    }
 
    return nChar;
}
 
Range ImpEditEngine::GetLineXPosStartEnd(ParaPortion const& rParaPortion, EditLine const& rLine) const
{
    Range aLineXPosStartEnd;
 
    sal_Int32 nPara = GetEditDoc().GetPos(rParaPortion.GetNode());
    if ( !IsRightToLeft( nPara ) )
    {
        aLineXPosStartEnd.Min() = rLine.GetStartPosX();
        aLineXPosStartEnd.Max() = rLine.GetStartPosX() + rLine.GetTextWidth();
    }
    else
    {
        aLineXPosStartEnd.Min() = GetPaperSize().Width() - (rLine.GetStartPosX() + rLine.GetTextWidth());
        aLineXPosStartEnd.Max() = GetPaperSize().Width() - rLine.GetStartPosX();
    }
 
    return aLineXPosStartEnd;
}
 
tools::Long ImpEditEngine::GetPortionXOffset(ParaPortion const& rParaPortion, EditLine const& rLine, sal_Int32 nTextPortion) const
{
    tools::Long nX = rLine.GetStartPosX();
 
    for ( sal_Int32 i = rLine.GetStartPortion(); i < nTextPortion; i++ )
    {
        const TextPortion& rPortion = rParaPortion.GetTextPortions()[i];
        switch ( rPortion.GetKind() )
        {
            case PortionKind::FIELD:
            case PortionKind::TEXT:
            case PortionKind::HYPHENATOR:
            case PortionKind::TAB:
            {
                nX += rPortion.GetSize().Width();
            }
            break;
            case PortionKind::LINEBREAK: break;
        }
    }
 
    sal_Int32 nPara = GetEditDoc().GetPos(rParaPortion.GetNode());
    bool bR2LPara = IsRightToLeft( nPara );
 
    const TextPortion& rDestPortion = rParaPortion.GetTextPortions()[nTextPortion];
    if ( rDestPortion.GetKind() != PortionKind::TAB )
    {
        if ( !bR2LPara && rDestPortion.GetRightToLeftLevel() )
        {
            // Portions behind must be added, visual before this portion
            sal_Int32 nTmpPortion = nTextPortion+1;
            while ( nTmpPortion <= rLine.GetEndPortion() )
            {
                const TextPortion& rNextTextPortion = rParaPortion.GetTextPortions()[nTmpPortion];
                if ( rNextTextPortion.GetRightToLeftLevel() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) )
                    nX += rNextTextPortion.GetSize().Width();
                else
                    break;
                nTmpPortion++;
            }
            // Portions before must be removed, visual behind this portion
            nTmpPortion = nTextPortion;
            while ( nTmpPortion > rLine.GetStartPortion() )
            {
                --nTmpPortion;
                const TextPortion& rPrevTextPortion = rParaPortion.GetTextPortions()[nTmpPortion];
                if ( rPrevTextPortion.GetRightToLeftLevel() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) )
                    nX -= rPrevTextPortion.GetSize().Width();
                else
                    break;
            }
        }
        else if ( bR2LPara && !rDestPortion.IsRightToLeft() )
        {
            // Portions behind must be removed, visual behind this portion
            sal_Int32 nTmpPortion = nTextPortion+1;
            while ( nTmpPortion <= rLine.GetEndPortion() )
            {
                const TextPortion& rNextTextPortion = rParaPortion.GetTextPortions()[nTmpPortion];
                if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) )
                    nX += rNextTextPortion.GetSize().Width();
                else
                    break;
                nTmpPortion++;
            }
            // Portions before must be added, visual before this portion
            nTmpPortion = nTextPortion;
            while ( nTmpPortion > rLine.GetStartPortion() )
            {
                --nTmpPortion;
                const TextPortion& rPrevTextPortion = rParaPortion.GetTextPortions()[nTmpPortion];
                if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) )
                    nX -= rPrevTextPortion.GetSize().Width();
                else
                    break;
            }
        }
    }
    if ( bR2LPara )
    {
        // Switch X positions...
        OSL_ENSURE( GetTextRanger() || GetPaperSize().Width(), "GetPortionXOffset - paper size?!" );
        OSL_ENSURE( GetTextRanger() || (nX <= GetPaperSize().Width()), "GetPortionXOffset - position out of paper size!" );
        nX = GetPaperSize().Width() - nX;
        nX -= rDestPortion.GetSize().Width();
    }
 
    return nX;
}
 
tools::Long ImpEditEngine::GetXPos(ParaPortion const& rParaPortion, EditLine const& rLine, sal_Int32 nIndex, bool bPreferPortionStart) const
{
    OSL_ENSURE( ( nIndex >= rLine.GetStart() ) && ( nIndex <= rLine.GetEnd() ) , "GetXPos has to be called properly!" );
 
    bool bDoPreferPortionStart = bPreferPortionStart;
    // Assure that the portion belongs to this line:
    if ( nIndex == rLine.GetStart() )
        bDoPreferPortionStart = true;
    else if ( nIndex == rLine.GetEnd() )
        bDoPreferPortionStart = false;
 
    sal_Int32 nTextPortionStart = 0;
    sal_Int32 nTextPortion = rParaPortion.GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart );
 
    OSL_ENSURE( ( nTextPortion >= rLine.GetStartPortion() ) && ( nTextPortion <= rLine.GetEndPortion() ), "GetXPos: Portion not in current line! " );
 
    const TextPortion& rPortion = rParaPortion.GetTextPortions()[nTextPortion];
 
    tools::Long nX = GetPortionXOffset(rParaPortion, rLine, nTextPortion);
 
    // calc text width, portion size may include CJK/CTL spacing...
    // But the array might not be init yet, if using text ranger this method is called within CreateLines()...
    tools::Long nPortionTextWidth = rPortion.GetSize().Width();
    if ( ( rPortion.GetKind() == PortionKind::TEXT ) && rPortion.GetLen() && !GetTextRanger() )
        nPortionTextWidth = rLine.GetCharPosArray()[nTextPortionStart + rPortion.GetLen() - 1 - rLine.GetStart()];
 
    if ( nTextPortionStart != nIndex )
    {
        // Search within portion...
        if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) )
        {
            // End of Portion
            if ( rPortion.GetKind() == PortionKind::TAB )
            {
                if ( nTextPortion+1 < rParaPortion.GetTextPortions().Count() )
                {
                    const TextPortion& rNextPortion = rParaPortion.GetTextPortions()[nTextPortion+1];
                    if ( rNextPortion.GetKind() != PortionKind::TAB )
                    {
                        if ( !bPreferPortionStart )
                            nX = GetXPos(rParaPortion, rLine, nIndex, true );
                        else if ( !IsRightToLeft( GetEditDoc().GetPos(rParaPortion.GetNode()) ) )
                            nX += nPortionTextWidth;
                    }
                }
                else if ( !IsRightToLeft( GetEditDoc().GetPos(rParaPortion.GetNode()) ) )
                {
                    nX += nPortionTextWidth;
                }
            }
            else if ( !rPortion.IsRightToLeft() )
            {
                nX += nPortionTextWidth;
            }
        }
        else if ( rPortion.GetKind() == PortionKind::TEXT )
        {
            OSL_ENSURE( nIndex != rLine.GetStart(), "Strange behavior in new GetXPos()" );
            OSL_ENSURE( !rLine.GetCharPosArray().empty(), "svx::ImpEditEngine::GetXPos(), portion in an empty line?" );
 
            if( !rLine.GetCharPosArray().empty() )
            {
                sal_Int32 nPos = nIndex - 1 - rLine.GetStart();
                if (nPos < 0 || o3tl::make_unsigned(nPos) >= rLine.GetCharPosArray().size())
                {
                    nPos = rLine.GetCharPosArray().size()-1;
                    OSL_FAIL("svx::ImpEditEngine::GetXPos(), index out of range!");
                }
 
                // old code restored see #i112788 (which leaves #i74188 unfixed again)
                tools::Long nPosInPortion = rLine.GetCharPosArray()[nPos];
 
                if ( !rPortion.IsRightToLeft() )
                {
                    nX += nPosInPortion;
                }
                else
                {
                    nX += nPortionTextWidth - nPosInPortion;
                }
 
                if ( rPortion.GetExtraInfos() && rPortion.GetExtraInfos()->bCompressed )
                {
                    nX += rPortion.GetExtraInfos()->nPortionOffsetX;
                    if ( rPortion.GetExtraInfos()->nAsianCompressionTypes & AsianCompressionFlags::PunctuationRight )
                    {
                        AsianCompressionFlags nType = GetCharTypeForCompression(rParaPortion.GetNode()->GetChar(nIndex));
                        if ( nType == AsianCompressionFlags::PunctuationRight && !rLine.GetCharPosArray().empty() )
                        {
                            sal_Int32 n = nIndex - nTextPortionStart;
                            const double* pDXArray = rLine.GetCharPosArray().data() + (nTextPortionStart - rLine.GetStart());
                            sal_Int32 nCharWidth = ( ( (n+1) < rPortion.GetLen() ) ? pDXArray[n] : rPortion.GetSize().Width() )
                                                            - ( n ? pDXArray[n-1] : 0 );
                            if ( (n+1) < rPortion.GetLen() )
                            {
                                // smaller, when char behind is AsianCompressionFlags::PunctuationRight also
                                nType = GetCharTypeForCompression(rParaPortion.GetNode()->GetChar(nIndex + 1));
                                if ( nType == AsianCompressionFlags::PunctuationRight )
                                {
                                    sal_Int32 nNextCharWidth = ( ( (n+2) < rPortion.GetLen() ) ? pDXArray[n+1] : rPortion.GetSize().Width() )
                                                                    - pDXArray[n];
                                    sal_Int32 nCompressed = nNextCharWidth/2;
                                    nCompressed *= rPortion.GetExtraInfos()->nMaxCompression100thPercent;
                                    nCompressed /= 10000;
                                    nCharWidth += nCompressed;
                                }
                            }
                            else
                            {
                                nCharWidth *= 2;    // last char pos to portion end is only compressed size
                            }
                            nX += nCharWidth/2; // 50% compression
                        }
                    }
                }
            }
        }
    }
    else // if ( nIndex == rLine.GetStart() )
    {
        if ( rPortion.IsRightToLeft() )
        {
            nX += nPortionTextWidth;
        }
    }
 
    return nX;
}
 
/** Is true if paragraph is in the empty cluster of paragraphs at the end */
bool ImpEditEngine::isInEmptyClusterAtTheEnd(ParaPortion& rPortion)
{
    sal_Int32 nPortion = GetParaPortions().GetPos(&rPortion);
 
    auto& rParagraphs = GetParaPortions();
    if (rParagraphs.Count() <= 0)
        return false;
 
    sal_Int32 nCurrent = rParagraphs.lastIndex();
    while (nCurrent > 0 && rParagraphs.getRef(nCurrent).IsEmpty())
    {
        if (nCurrent == nPortion)
            return true;
        nCurrent--;
    }
    return false;
}
 
void ImpEditEngine::CalcHeight(ParaPortion& rPortion)
{
    rPortion.mnHeight = 0;
    rPortion.mnFirstLineOffset = 0;
 
    if (!rPortion.IsVisible() || isInEmptyClusterAtTheEnd(rPortion))
        return;
 
    OSL_ENSURE(rPortion.GetLines().Count(), "Paragraph with no lines in ParaPortion::CalcHeight");
    for (sal_Int32 nLine = 0; nLine < rPortion.GetLines().Count(); ++nLine)
        rPortion.mnHeight += rPortion.GetLines()[nLine].GetHeight();
 
    if (maStatus.IsOutliner())
        return;
 
    const SvxULSpaceItem& rULItem = rPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
    const SvxLineSpacingItem& rLSItem = rPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
    sal_Int32 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0;
 
    if ( nSBL )
    {
        if (rPortion.GetLines().Count() > 1)
            rPortion.mnHeight += (rPortion.GetLines().Count() - 1) * nSBL;
        if (maStatus.ULSpaceSummation())
            rPortion.mnHeight += nSBL;
    }
 
    sal_Int32 nPortion = GetParaPortions().GetPos(&rPortion);
    if ( nPortion )
    {
        sal_uInt16 nUpper = scaleYSpacingValue(rULItem.GetUpper());
        rPortion.mnHeight += nUpper;
        rPortion.mnFirstLineOffset = nUpper;
    }
 
    if (nPortion != GetParaPortions().lastIndex())
    {
        rPortion.mnHeight += scaleYSpacingValue(rULItem.GetLower());   // not in the last
    }
 
    if ( !nPortion || maStatus.ULSpaceSummation() )
        return;
 
    ParaPortion* pPrev = GetParaPortions().SafeGetObject( nPortion-1 );
    if (!pPrev)
        return;
 
    const SvxULSpaceItem& rPrevULItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
    const SvxLineSpacingItem& rPrevLSItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
 
    // In relation between WinWord6/Writer3:
    // With a proportional line spacing the paragraph spacing is
    // also manipulated.
    // Only Writer3: Do not add up, but minimum distance.
 
    // check if distance by LineSpacing > Upper:
    sal_uInt16 nExtraSpace = scaleYSpacingValue(lcl_CalcExtraSpace(rLSItem));
    if (nExtraSpace > rPortion.mnFirstLineOffset)
    {
        // Paragraph becomes 'bigger':
        rPortion.mnHeight += (nExtraSpace - rPortion.mnFirstLineOffset);
        rPortion.mnFirstLineOffset = nExtraSpace;
    }
 
    // Determine nFirstLineOffset now f(pNode) => now f(pNode, pPrev):
    sal_uInt16 nPrevLower = scaleYSpacingValue(rPrevULItem.GetLower());
 
    // This PrevLower is still in the height of PrevPortion ...
    if (nPrevLower > rPortion.mnFirstLineOffset)
    {
        // Paragraph is 'small':
        rPortion.mnHeight -= rPortion.mnFirstLineOffset;
        rPortion.mnFirstLineOffset = 0;
    }
    else if ( nPrevLower )
    {
        // Paragraph becomes 'somewhat smaller':
        rPortion.mnHeight -= nPrevLower;
        rPortion.mnFirstLineOffset = rPortion.mnFirstLineOffset - nPrevLower;
    }
    // I find it not so good, but Writer3 feature:
    // Check if distance by LineSpacing > Lower: this value is not
    // stuck in the height of PrevPortion.
    if ( pPrev->IsInvalid() )
        return;
 
    nExtraSpace = scaleYSpacingValue(lcl_CalcExtraSpace(rPrevLSItem));
    if ( nExtraSpace > nPrevLower )
    {
        sal_uInt16 nMoreLower = nExtraSpace - nPrevLower;
        // Paragraph becomes 'bigger', 'grows' downwards:
        if ( nMoreLower > rPortion.mnFirstLineOffset )
        {
            rPortion.mnHeight += (nMoreLower - rPortion.mnFirstLineOffset);
            rPortion.mnFirstLineOffset = nMoreLower;
        }
    }
}
 
void ImpEditEngine::SetPaperSize(const Size& rNewSize)
{
    Size aOldSize = maPaperSize;
    SetValidPaperSize(rNewSize);
    Size aNewSize = maPaperSize;
 
    bool bAutoPageSize = GetStatus().AutoPageSize();
    if ( !(bAutoPageSize || ( aNewSize.Width() != aOldSize.Width() )) )
        return;
 
    for (EditView* pView : maEditViews)
    {
        if ( bAutoPageSize )
            pView->getImpl().RecalcOutputArea();
        else if (pView->getImpl().DoAutoSize())
        {
            pView->getImpl().ResetOutputArea(tools::Rectangle(pView->getImpl().GetOutputArea().TopLeft(), aNewSize));
        }
    }
 
    if ( bAutoPageSize || IsFormatted() )
    {
        // Changing the width has no effect for AutoPageSize, as this is
        // determined by the text width.
        // Optimization first after Vobis delivery was enabled ...
        FormatFullDoc();
 
        UpdateViews(mpActiveView);
 
        if (IsUpdateLayout() && mpActiveView)
            mpActiveView->ShowCursor(false, false);
    }
}
 
void ImpEditEngine::SetValidPaperSize( const Size& rNewSz )
{
    maPaperSize = rNewSz;
 
    tools::Long nMinWidth = maStatus.AutoPageWidth() ? maMinAutoPaperSize.Width() : 0;
    tools::Long nMaxWidth = maStatus.AutoPageWidth() ? maMaxAutoPaperSize.Width() : 0x7FFFFFFF;
    tools::Long nMinHeight = maStatus.AutoPageHeight() ? maMinAutoPaperSize.Height() : 0;
    tools::Long nMaxHeight = maStatus.AutoPageHeight() ? maMaxAutoPaperSize.Height() : 0x7FFFFFFF;
 
    // Minimum/Maximum width:
    if ( maPaperSize.Width() < nMinWidth )
        maPaperSize.setWidth( nMinWidth );
    else if ( maPaperSize.Width() > nMaxWidth )
        maPaperSize.setWidth( nMaxWidth );
 
    // Minimum/Maximum height:
    if ( maPaperSize.Height() < nMinHeight )
        maPaperSize.setHeight( nMinHeight );
    else if ( maPaperSize.Height() > nMaxHeight )
        maPaperSize.setHeight( nMaxHeight );
}
 
std::shared_ptr<SvxForbiddenCharactersTable> const & ImpEditEngine::GetForbiddenCharsTable()
{
    return EditDLL::Get().GetGlobalData()->GetForbiddenCharsTable();
}
 
void ImpEditEngine::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars)
{
    EditDLL::Get().GetGlobalData()->SetForbiddenCharsTable( xForbiddenChars );
}
 
bool ImpEditEngine::IsVisualCursorTravelingEnabled()
{
    bool bVisualCursorTravaling = false;
 
    if ( SvtCTLOptions::IsCTLFontEnabled() && ( SvtCTLOptions::GetCTLCursorMovement() == SvtCTLOptions::MOVEMENT_VISUAL ) )
    {
        bVisualCursorTravaling = true;
    }
 
    return bVisualCursorTravaling;
 
}
 
bool ImpEditEngine::DoVisualCursorTraveling()
{
    // Don't check if it's necessary, because we also need it when leaving the paragraph
    return IsVisualCursorTravelingEnabled();
}
 
IMPL_LINK_NOARG(ImpEditEngine, DocModified, LinkParamNone*, void)
{
    maModifyHdl.Call( nullptr /*GetEditEnginePtr()*/ ); // NULL, because also used for Outliner
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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

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

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

V547 Expression 'nDiffRight >= 0' is always true.

V728 An excessive check can be simplified. The '(A && B) || (!A && !B)' expression is equivalent to the 'bool(A) == bool(B)' expression.

V788 The variable 'lastCol', captured in a lambda expression, has a constant value.

V788 The variable 'minHeight', captured in a lambda expression, has a constant value.

V1051 Consider checking for misprints. It's possible that the 'bGotoStartOfNextLine' should be checked here.