xref: /core/editeng/source/editeng/impedit2.cxx (revision a682d129)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <vcl/svapp.hxx>
21 #include <vcl/window.hxx>
22 #include <editeng/lspcitem.hxx>
23 #include <editeng/flditem.hxx>
24 #include "impedit.hxx"
25 #include <editeng/editeng.hxx>
26 #include <editeng/editview.hxx>
27 #include "editdbg.hxx"
28 #include <eerdll2.hxx>
29 #include <editeng/eerdll.hxx>
30 #include <edtspell.hxx>
31 #include "eeobj.hxx"
32 #include <editeng/txtrange.hxx>
33 #include <svl/urlbmk.hxx>
34 #include <sfx2/app.hxx>
35 #include <svtools/colorcfg.hxx>
36 #include <svl/ctloptions.hxx>
37 #include <editeng/acorrcfg.hxx>
38 #include <editeng/fhgtitem.hxx>
39 #include <editeng/lrspitem.hxx>
40 #include <editeng/ulspitem.hxx>
41 #include <editeng/wghtitem.hxx>
42 #include <editeng/postitem.hxx>
43 #include <editeng/udlnitem.hxx>
44 #include <editeng/adjustitem.hxx>
45 #include <editeng/scripttypeitem.hxx>
46 #include <editeng/frmdiritem.hxx>
47 #include <editeng/fontitem.hxx>
48 #include <editeng/justifyitem.hxx>
49 
50 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
51 #include <com/sun/star/i18n/WordType.hpp>
52 #include <com/sun/star/i18n/ScriptType.hpp>
53 #include <com/sun/star/lang/Locale.hpp>
54 #include <com/sun/star/text/CharacterCompressionType.hpp>
55 #include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
56 
57 
58 #include <sal/log.hxx>
59 #include <osl/diagnose.h>
60 #include <sot/exchange.hxx>
61 #include <sot/formats.hxx>
62 #include <svl/asiancfg.hxx>
63 #include <i18nutil/unicode.hxx>
64 #include <tools/diagnose_ex.h>
65 #include <comphelper/lok.hxx>
66 #include <unotools/configmgr.hxx>
67 
68 #include <unicode/ubidi.h>
69 #include <algorithm>
70 #include <memory>
71 
72 #include <iostream>
73 #include <fstream>
74 
75 using namespace ::com::sun::star;
76 
77 static sal_uInt16 lcl_CalcExtraSpace( const SvxLineSpacingItem& rLSItem )
78 {
79     sal_uInt16 nExtra = 0;
80     if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
81     {
82         nExtra = rLSItem.GetInterLineSpace();
83     }
84 
85     return nExtra;
86 }
87 
88 ImpEditEngine::ImpEditEngine( EditEngine* pEE, SfxItemPool* pItemPool ) :
89     pSharedVCL(EditDLL::Get().GetSharedVclResources()),
90     aPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ),
91     aMinAutoPaperSize( 0x0, 0x0 ),
92     aMaxAutoPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ),
93     aEditDoc( pItemPool ),
94     aWordDelimiters(" .,;:-`'?!_=\"{}()[]"),
95     bKernAsianPunctuation(false),
96     bAddExtLeading(false),
97     bIsFormatting(false),
98     bFormatted(false),
99     bInSelection(false),
100     bIsInUndo(false),
101     bUpdate(true),
102     bUndoEnabled(true),
103     bDowning(false),
104     bUseAutoColor(true),
105     bForceAutoColor(false),
106     bCallParaInsertedOrDeleted(false),
107     bFirstWordCapitalization(true),
108     mbLastTryMerge(false),
109     mbReplaceLeadingSingleQuotationMark(true),
110     mbNbspRunNext(false)
111 {
112     pEditEngine         = pEE;
113     pActiveView         = nullptr;
114     pConvInfo           = nullptr;
115     pTextObjectPool     = nullptr;
116     pStylePool          = nullptr;
117     pUndoManager        = nullptr;
118     pUndoMarkSelection  = nullptr;
119 
120     nCurTextHeight      = 0;
121     nCurTextHeightNTP   = 0;
122     nBigTextObjectStart = 20;
123 
124     nStretchX           = 100;
125     nStretchY           = 100;
126 
127     eDefLanguage        = LANGUAGE_DONTKNOW;
128     maBackgroundColor   = COL_AUTO;
129 
130     nAsianCompressionMode = CharCompressType::NONE;
131 
132     eDefaultHorizontalTextDirection = EEHorizontalTextDirection::Default;
133 
134 
135     aStatus.GetControlWord() =  EEControlBits::USECHARATTRIBS | EEControlBits::DOIDLEFORMAT |
136                                 EEControlBits::PASTESPECIAL | EEControlBits::UNDOATTRIBS |
137                                 EEControlBits::ALLOWBIGOBJS | EEControlBits::RTFSTYLESHEETS |
138                                 EEControlBits::FORMAT100;
139 
140     aSelEngine.SetFunctionSet( &aSelFuncSet );
141 
142     aStatusTimer.SetTimeout( 200 );
143     aStatusTimer.SetInvokeHandler( LINK( this, ImpEditEngine, StatusTimerHdl ) );
144     aStatusTimer.SetDebugName( "editeng::ImpEditEngine aStatusTimer" );
145 
146     aIdleFormatter.SetPriority( TaskPriority::REPAINT );
147     aIdleFormatter.SetInvokeHandler( LINK( this, ImpEditEngine, IdleFormatHdl ) );
148     aIdleFormatter.SetDebugName( "editeng::ImpEditEngine aIdleFormatter" );
149 
150     aOnlineSpellTimer.SetTimeout( 100 );
151     aOnlineSpellTimer.SetInvokeHandler( LINK( this, ImpEditEngine, OnlineSpellHdl ) );
152     aOnlineSpellTimer.SetDebugName( "editeng::ImpEditEngine aOnlineSpellTimer" );
153 
154     // Access data already from here on!
155     SetRefDevice( nullptr );
156     InitDoc( false );
157 
158     bCallParaInsertedOrDeleted = true;
159 
160     aEditDoc.SetModifyHdl( LINK( this, ImpEditEngine, DocModified ) );
161     StartListening(*SfxGetpApp());
162 }
163 
164 void ImpEditEngine::Dispose()
165 {
166     SolarMutexGuard g;
167     auto pApp = SfxApplication::Get();
168     if(pApp)
169         EndListening(*pApp);
170     pVirtDev.disposeAndClear();
171     mpOwnDev.disposeAndClear();
172     pSharedVCL.reset();
173 }
174 
175 ImpEditEngine::~ImpEditEngine()
176 {
177     aStatusTimer.Stop();
178     aOnlineSpellTimer.Stop();
179     aIdleFormatter.Stop();
180 
181     // Destroying templates may otherwise cause unnecessary formatting,
182     // when a parent template is destroyed.
183     // And this after the destruction of the data!
184     bDowning = true;
185     SetUpdateMode( false );
186 
187     Dispose();
188     // it's only legal to delete the pUndoManager if it was created by
189     // ImpEditEngine; if it was set by SetUndoManager() it must be cleared
190     // before destroying the ImpEditEngine!
191     assert(!pUndoManager || typeid(*pUndoManager) == typeid(EditUndoManager));
192     delete pUndoManager;
193     pTextRanger.reset();
194     mpIMEInfos.reset();
195     pCTLOptions.reset();
196     pSpellInfo.reset();
197 }
198 
199 void ImpEditEngine::SetRefDevice( OutputDevice* pRef )
200 {
201     if (pRef)
202         pRefDev = pRef;
203     else
204         pRefDev = pSharedVCL->GetVirtualDevice();
205 
206     nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width());
207 
208     if ( IsFormatted() )
209     {
210         FormatFullDoc();
211         UpdateViews();
212     }
213 }
214 
215 void ImpEditEngine::SetRefMapMode( const MapMode& rMapMode )
216 {
217     if ( GetRefDevice()->GetMapMode() == rMapMode )
218         return;
219 
220     mpOwnDev.disposeAndClear();
221     mpOwnDev = VclPtr<VirtualDevice>::Create();
222     pRefDev = mpOwnDev;
223     pRefDev->SetMapMode(MapMode(MapUnit::MapTwip));
224     SetRefDevice( pRefDev );
225 
226     pRefDev->SetMapMode( rMapMode );
227     nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width());
228     if ( IsFormatted() )
229     {
230         FormatFullDoc();
231         UpdateViews();
232     }
233 }
234 
235 void ImpEditEngine::InitDoc(bool bKeepParaAttribs)
236 {
237     sal_Int32 nParas = aEditDoc.Count();
238     for ( sal_Int32 n = bKeepParaAttribs ? 1 : 0; n < nParas; n++ )
239     {
240         if ( aEditDoc[n]->GetStyleSheet() )
241             EndListening( *aEditDoc[n]->GetStyleSheet() );
242     }
243 
244     if ( bKeepParaAttribs )
245         aEditDoc.RemoveText();
246     else
247         aEditDoc.Clear();
248 
249     GetParaPortions().Reset();
250 
251     GetParaPortions().Insert(0, std::make_unique<ParaPortion>( aEditDoc[0] ));
252 
253     bFormatted = false;
254 
255     if ( IsCallParaInsertedOrDeleted() )
256     {
257         GetEditEnginePtr()->ParagraphDeleted( EE_PARA_ALL );
258         GetEditEnginePtr()->ParagraphInserted( 0 );
259     }
260 
261     if ( GetStatus().DoOnlineSpelling() )
262         aEditDoc.GetObject( 0 )->CreateWrongList();
263 }
264 
265 EditPaM ImpEditEngine::DeleteSelected(const EditSelection& rSel)
266 {
267     EditPaM aPaM (ImpDeleteSelection(rSel));
268     return aPaM;
269 }
270 
271 OUString ImpEditEngine::GetSelected( const EditSelection& rSel  ) const
272 {
273     if ( !rSel.HasRange() )
274         return OUString();
275 
276     EditSelection aSel( rSel );
277     aSel.Adjust( aEditDoc );
278 
279     ContentNode* pStartNode = aSel.Min().GetNode();
280     ContentNode* pEndNode = aSel.Max().GetNode();
281     sal_Int32 nStartNode = aEditDoc.GetPos( pStartNode );
282     sal_Int32 nEndNode = aEditDoc.GetPos( pEndNode );
283 
284     OSL_ENSURE( nStartNode <= nEndNode, "Selection not sorted ?" );
285 
286     OUStringBuffer aText;
287     const OUString aSep = EditDoc::GetSepStr( LINEEND_LF );
288 
289     // iterate over the paragraphs ...
290     for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ )
291     {
292         OSL_ENSURE( aEditDoc.GetObject( nNode ), "Node not found: GetSelected" );
293         const ContentNode* pNode = aEditDoc.GetObject( nNode );
294 
295         const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0;
296         const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart!
297 
298         aText.append(EditDoc::GetParaAsString( pNode, nStartPos, nEndPos ));
299         if ( nNode < nEndNode )
300             aText.append(aSep);
301     }
302     return aText.makeStringAndClear();
303 }
304 
305 bool ImpEditEngine::MouseButtonDown( const MouseEvent& rMEvt, EditView* pView )
306 {
307     GetSelEngine().SetCurView( pView );
308     SetActiveView( pView );
309 
310     if (!GetAutoCompleteText().isEmpty())
311         SetAutoCompleteText( OUString(), true );
312 
313     GetSelEngine().SelMouseButtonDown( rMEvt );
314     // Special treatment
315     EditSelection aCurSel( pView->pImpEditView->GetEditSelection() );
316     if ( !rMEvt.IsShift() )
317     {
318         if ( rMEvt.GetClicks() == 2 )
319         {
320             // So that the SelectionEngine knows about the anchor.
321             aSelEngine.CursorPosChanging( true, false );
322 
323             EditSelection aNewSelection( SelectWord( aCurSel ) );
324             pView->pImpEditView->DrawSelectionXOR();
325             pView->pImpEditView->SetEditSelection( aNewSelection );
326             pView->pImpEditView->DrawSelectionXOR();
327             pView->ShowCursor();
328         }
329         else if ( rMEvt.GetClicks() == 3 )
330         {
331             // So that the SelectionEngine knows about the anchor.
332             aSelEngine.CursorPosChanging( true, false );
333 
334             EditSelection aNewSelection( aCurSel );
335             aNewSelection.Min().SetIndex( 0 );
336             aNewSelection.Max().SetIndex( aCurSel.Min().GetNode()->Len() );
337             pView->pImpEditView->DrawSelectionXOR();
338             pView->pImpEditView->SetEditSelection( aNewSelection );
339             pView->pImpEditView->DrawSelectionXOR();
340             pView->ShowCursor();
341         }
342     }
343     return true;
344 }
345 
346 void ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView )
347 {
348     GetSelEngine().SetCurView( pView );
349     SetActiveView( pView );
350     if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
351     {
352         pView->DeleteSelected();
353         mpIMEInfos.reset();
354         EditPaM aPaM = pView->GetImpEditView()->GetEditSelection().Max();
355         OUString aOldTextAfterStartPos = aPaM.GetNode()->Copy( aPaM.GetIndex() );
356         sal_Int32 nMax = aOldTextAfterStartPos.indexOf( CH_FEATURE );
357         if ( nMax != -1 )  // don't overwrite features!
358             aOldTextAfterStartPos = aOldTextAfterStartPos.copy( 0, nMax );
359         mpIMEInfos.reset( new ImplIMEInfos( aPaM, aOldTextAfterStartPos ) );
360         mpIMEInfos->bWasCursorOverwrite = !pView->IsInsertMode();
361         UndoActionStart( EDITUNDO_INSERT );
362     }
363     else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
364     {
365         OSL_ENSURE( mpIMEInfos, "CommandEventId::EndExtTextInput => No start ?" );
366         if( mpIMEInfos )
367         {
368             // #102812# convert quotes in IME text
369             // works on the last input character, this is especially in Korean text often done
370             // quotes that are inside of the string are not replaced!
371             // Borrowed from sw: edtwin.cxx
372             if ( mpIMEInfos->nLen )
373             {
374                 EditSelection aSel( mpIMEInfos->aPos );
375                 aSel.Min().SetIndex( aSel.Min().GetIndex() + mpIMEInfos->nLen-1 );
376                 aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen );
377                 // #102812# convert quotes in IME text
378                 // works on the last input character, this is especially in Korean text often done
379                 // quotes that are inside of the string are not replaced!
380                 const sal_Unicode nCharCode = aSel.Min().GetNode()->GetChar( aSel.Min().GetIndex() );
381                 if ( ( GetStatus().DoAutoCorrect() ) && ( ( nCharCode == '\"' ) || ( nCharCode == '\'' ) ) )
382                 {
383                     aSel = DeleteSelected( aSel );
384                     aSel = AutoCorrect( aSel, nCharCode, mpIMEInfos->bWasCursorOverwrite );
385                     pView->pImpEditView->SetEditSelection( aSel );
386                 }
387             }
388 
389             ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() );
390             pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() );
391 
392             bool bWasCursorOverwrite = mpIMEInfos->bWasCursorOverwrite;
393 
394             mpIMEInfos.reset();
395 
396             FormatAndUpdate( pView );
397 
398             pView->SetInsertMode( !bWasCursorOverwrite );
399         }
400         UndoActionEnd();
401     }
402     else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
403     {
404         OSL_ENSURE( mpIMEInfos, "CommandEventId::ExtTextInput => No Start ?" );
405         if( mpIMEInfos )
406         {
407             const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
408 
409             if ( !pData->IsOnlyCursorChanged() )
410             {
411                 EditSelection aSel( mpIMEInfos->aPos );
412                 aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen );
413                 aSel = DeleteSelected( aSel );
414                 aSel = ImpInsertText( aSel, pData->GetText() );
415 
416                 if ( mpIMEInfos->bWasCursorOverwrite )
417                 {
418                     sal_Int32 nOldIMETextLen = mpIMEInfos->nLen;
419                     sal_Int32 nNewIMETextLen = pData->GetText().getLength();
420 
421                     if ( ( nOldIMETextLen > nNewIMETextLen ) &&
422                          ( nNewIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
423                     {
424                         // restore old characters
425                         sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
426                         EditPaM aPaM( mpIMEInfos->aPos );
427                         aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen );
428                         ImpInsertText( aPaM, mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) );
429                     }
430                     else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
431                               ( nOldIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
432                     {
433                         // overwrite
434                         sal_Int32 nOverwrite = nNewIMETextLen - nOldIMETextLen;
435                         if ( ( nOldIMETextLen + nOverwrite ) > mpIMEInfos->aOldTextAfterStartPos.getLength() )
436                             nOverwrite = mpIMEInfos->aOldTextAfterStartPos.getLength() - nOldIMETextLen;
437                         OSL_ENSURE( nOverwrite && (nOverwrite < 0xFF00), "IME Overwrite?!" );
438                         EditPaM aPaM( mpIMEInfos->aPos );
439                         aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen );
440                         EditSelection _aSel( aPaM );
441                         _aSel.Max().SetIndex( _aSel.Max().GetIndex() + nOverwrite );
442                         DeleteSelected( _aSel );
443                     }
444                 }
445                 if ( pData->GetTextAttr() )
446                 {
447                     mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
448                 }
449                 else
450                 {
451                     mpIMEInfos->DestroyAttribs();
452                     mpIMEInfos->nLen = pData->GetText().getLength();
453                 }
454 
455                 ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() );
456                 pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() );
457                 FormatAndUpdate( pView );
458             }
459 
460             EditSelection aNewSel = EditPaM( mpIMEInfos->aPos.GetNode(), mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() );
461             pView->SetSelection( CreateESel( aNewSel ) );
462             pView->SetInsertMode( !pData->IsCursorOverwrite() );
463 
464             if ( pData->IsCursorVisible() )
465                 pView->ShowCursor();
466             else
467                 pView->HideCursor();
468         }
469     }
470     else if ( rCEvt.GetCommand() == CommandEventId::InputContextChange )
471     {
472     }
473     else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
474     {
475         if ( mpIMEInfos && mpIMEInfos->nLen )
476         {
477             EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() );
478             tools::Rectangle aR1 = PaMtoEditCursor( aPaM );
479 
480             sal_Int32 nInputEnd = mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen;
481 
482             if ( !IsFormatted() )
483                 FormatDoc();
484 
485             ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( GetEditDoc().GetPos( aPaM.GetNode() ) );
486             if (pParaPortion)
487             {
488                 sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), true );
489                 const EditLine& rLine = pParaPortion->GetLines()[nLine];
490                 if ( nInputEnd > rLine.GetEnd() )
491                     nInputEnd = rLine.GetEnd();
492                 tools::Rectangle aR2 = PaMtoEditCursor( EditPaM( aPaM.GetNode(), nInputEnd ), GetCursorFlags::EndOfLine );
493                 tools::Rectangle aRect = pView->GetImpEditView()->GetWindowPos( aR1 );
494                 pView->GetWindow()->SetCursorRect( &aRect, aR2.Left()-aR1.Right() );
495             }
496         }
497         else
498         {
499             pView->GetWindow()->SetCursorRect();
500         }
501     }
502     else if ( rCEvt.GetCommand() == CommandEventId::SelectionChange )
503     {
504         const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData();
505 
506         ESelection aSelection = pView->GetSelection();
507         aSelection.Adjust();
508 
509         if( pView->HasSelection() )
510         {
511             aSelection.nEndPos = aSelection.nStartPos;
512             aSelection.nStartPos += pData->GetStart();
513             aSelection.nEndPos += pData->GetEnd();
514         }
515         else
516         {
517             aSelection.nStartPos = pData->GetStart();
518             aSelection.nEndPos = pData->GetEnd();
519         }
520         pView->SetSelection( aSelection );
521     }
522     else if ( rCEvt.GetCommand() == CommandEventId::PrepareReconversion )
523     {
524         if ( pView->HasSelection() )
525         {
526             ESelection aSelection = pView->GetSelection();
527             aSelection.Adjust();
528 
529             if ( aSelection.nStartPara != aSelection.nEndPara )
530             {
531                 sal_Int32 aParaLen = pEditEngine->GetTextLen( aSelection.nStartPara );
532                 aSelection.nEndPara = aSelection.nStartPara;
533                 aSelection.nEndPos = aParaLen;
534                 pView->SetSelection( aSelection );
535             }
536         }
537     }
538     else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition )
539     {
540         if ( mpIMEInfos && mpIMEInfos->nLen )
541         {
542             EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() );
543             if ( !IsFormatted() )
544                 FormatDoc();
545 
546             ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( GetEditDoc().GetPos( aPaM.GetNode() ) );
547             if (pParaPortion)
548             {
549                 sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), true );
550                 const EditLine& rLine = pParaPortion->GetLines()[nLine];
551                 std::unique_ptr<tools::Rectangle[]> aRects(new tools::Rectangle[ mpIMEInfos->nLen ]);
552                 for (sal_Int32 i = 0; i < mpIMEInfos->nLen; ++i)
553                 {
554                     sal_Int32 nInputPos = mpIMEInfos->aPos.GetIndex() + i;
555                     if ( nInputPos > rLine.GetEnd() )
556                         nInputPos = rLine.GetEnd();
557                     tools::Rectangle aR2 = GetEditCursor( pParaPortion, nInputPos );
558                     aRects[ i ] = pView->GetImpEditView()->GetWindowPos( aR2 );
559                 }
560                 pView->GetWindow()->SetCompositionCharRect( aRects.get(), mpIMEInfos->nLen );
561             }
562         }
563     }
564 
565     GetSelEngine().Command( rCEvt );
566 }
567 
568 bool ImpEditEngine::MouseButtonUp( const MouseEvent& rMEvt, EditView* pView )
569 {
570     GetSelEngine().SetCurView( pView );
571     GetSelEngine().SelMouseButtonUp( rMEvt );
572 
573     // in the tiled rendering case, setting bInSelection here has unexpected
574     // consequences - further tiles painting removes the selection
575     // FIXME I believe resetting bInSelection should not be here even in the
576     // non-tiled-rendering case, but it has been here since 2000 (and before)
577     // so who knows what corner case it was supposed to solve back then
578     if (!comphelper::LibreOfficeKit::isActive())
579         bInSelection = false;
580 
581     // Special treatments
582     EditSelection aCurSel( pView->pImpEditView->GetEditSelection() );
583     if ( !aCurSel.HasRange() )
584     {
585         if ( ( rMEvt.GetClicks() == 1 ) && rMEvt.IsLeft() && !rMEvt.IsMod2() )
586         {
587             const OutputDevice& rOutDev = pView->getEditViewCallbacks() ? pView->getEditViewCallbacks()->EditViewOutputDevice() : *pView->GetWindow();
588             Point aLogicClick = rOutDev.PixelToLogic(rMEvt.GetPosPixel());
589             if (const SvxFieldItem* pFld = pView->GetField(aLogicClick))
590             {
591                 EditPaM aPaM( aCurSel.Max() );
592                 sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() );
593                 GetEditEnginePtr()->FieldClicked( *pFld, nPara, aPaM.GetIndex() );
594             }
595         }
596     }
597     return true;
598 }
599 
600 void ImpEditEngine::ReleaseMouse()
601 {
602     GetSelEngine().ReleaseMouse();
603 }
604 
605 bool ImpEditEngine::MouseMove( const MouseEvent& rMEvt, EditView* pView )
606 {
607     // MouseMove is called directly after ShowQuickHelp()!
608     GetSelEngine().SetCurView( pView );
609     GetSelEngine().SelMouseMove( rMEvt );
610     return true;
611 }
612 
613 EditPaM ImpEditEngine::InsertText(const EditSelection& aSel, const OUString& rStr)
614 {
615     EditPaM aPaM = ImpInsertText( aSel, rStr );
616     return aPaM;
617 }
618 
619 void ImpEditEngine::Clear()
620 {
621     InitDoc( false );
622 
623     EditPaM aPaM = aEditDoc.GetStartPaM();
624     EditSelection aSel( aPaM );
625 
626     nCurTextHeight = 0;
627     nCurTextHeightNTP = 0;
628 
629     ResetUndoManager();
630 
631     for (size_t nView = aEditViews.size(); nView; )
632     {
633         EditView* pView = aEditViews[--nView];
634         pView->pImpEditView->SetEditSelection( aSel );
635     }
636 }
637 
638 EditPaM ImpEditEngine::RemoveText()
639 {
640     InitDoc( true );
641 
642     EditPaM aStartPaM = aEditDoc.GetStartPaM();
643     EditSelection aEmptySel( aStartPaM, aStartPaM );
644     for (EditView* pView : aEditViews)
645     {
646         pView->pImpEditView->SetEditSelection( aEmptySel );
647     }
648     ResetUndoManager();
649     return aEditDoc.GetStartPaM();
650 }
651 
652 
653 void ImpEditEngine::SetText(const OUString& rText)
654 {
655     // RemoveText deletes the undo list!
656     EditPaM aStartPaM = RemoveText();
657     bool bUndoCurrentlyEnabled = IsUndoEnabled();
658     // The text inserted manually can not be made reversible by the user
659     EnableUndo( false );
660 
661     EditSelection aEmptySel( aStartPaM, aStartPaM );
662     EditPaM aPaM = aStartPaM;
663     if (!rText.isEmpty())
664         aPaM = ImpInsertText( aEmptySel, rText );
665 
666     for (EditView* pView : aEditViews)
667     {
668         pView->pImpEditView->SetEditSelection( EditSelection( aPaM, aPaM ) );
669         //  If no text then also no Format&Update
670         // => The text remains.
671         if (rText.isEmpty() && GetUpdateMode())
672         {
673             tools::Rectangle aTmpRect( pView->GetOutputArea().TopLeft(),
674                                 Size( aPaperSize.Width(), nCurTextHeight ) );
675             aTmpRect.Intersection( pView->GetOutputArea() );
676             pView->InvalidateWindow( aTmpRect );
677         }
678     }
679     if (rText.isEmpty()) {    // otherwise it must be invalidated later, !bFormatted is enough.
680         nCurTextHeight = 0;
681         nCurTextHeightNTP = 0;
682     }
683     EnableUndo( bUndoCurrentlyEnabled );
684     OSL_ENSURE( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "Undo after SetText?" );
685 }
686 
687 
688 const SfxItemSet& ImpEditEngine::GetEmptyItemSet()
689 {
690     if ( !pEmptyItemSet )
691     {
692         pEmptyItemSet = std::make_unique<SfxItemSet>( aEditDoc.GetItemPool(), svl::Items<EE_ITEMS_START, EE_ITEMS_END>{} );
693         for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++)
694         {
695             pEmptyItemSet->ClearItem( nWhich );
696         }
697     }
698     return *pEmptyItemSet;
699 }
700 
701 
702 //  MISC
703 
704 void ImpEditEngine::CursorMoved( const ContentNode* pPrevNode )
705 {
706     // Delete empty attributes, but only if paragraph is not empty!
707     if (pPrevNode->GetCharAttribs().HasEmptyAttribs() && pPrevNode->Len())
708     {
709         const_cast<ContentNode*>(pPrevNode)->GetCharAttribs().DeleteEmptyAttribs(aEditDoc.GetItemPool());
710     }
711 }
712 
713 void ImpEditEngine::TextModified()
714 {
715     bFormatted = false;
716 
717     if ( GetNotifyHdl().IsSet() )
718     {
719         EENotify aNotify( EE_NOTIFY_TEXTMODIFIED );
720         GetNotifyHdl().Call( aNotify );
721     }
722 }
723 
724 
725 void ImpEditEngine::ParaAttribsChanged( ContentNode const * pNode, bool bIgnoreUndoCheck )
726 {
727     assert(pNode && "ParaAttribsChanged: Which one?");
728 
729     aEditDoc.SetModified( true );
730     bFormatted = false;
731 
732     ParaPortion* pPortion = FindParaPortion( pNode );
733     OSL_ENSURE( pPortion, "ParaAttribsChanged: Portion?" );
734     pPortion->MarkSelectionInvalid( 0 );
735 
736     sal_Int32 nPara = aEditDoc.GetPos( pNode );
737     if ( bIgnoreUndoCheck || pEditEngine->IsInUndo() )
738         pEditEngine->ParaAttribsChanged( nPara );
739 
740     ParaPortion* pNextPortion = GetParaPortions().SafeGetObject( nPara+1 );
741     // => is formatted again anyway, if Invalid.
742     if ( pNextPortion && !pNextPortion->IsInvalid() )
743         CalcHeight( pNextPortion );
744 }
745 
746 
747 //  Cursor movements
748 
749 
750 EditSelection const & ImpEditEngine::MoveCursor( const KeyEvent& rKeyEvent, EditView* pEditView )
751 {
752     // Actually, only necessary for up/down, but whatever.
753     CheckIdleFormatter();
754 
755     EditPaM aPaM( pEditView->pImpEditView->GetEditSelection().Max() );
756 
757     EditPaM aOldPaM( aPaM );
758 
759     TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom;
760     if (IsVertical() && IsTopToBottom())
761         eTextDirection = TextDirectionality::TopToBottom_RightToLeft;
762     else if (IsVertical() && !IsTopToBottom())
763         eTextDirection = TextDirectionality::BottomToTop_LeftToRight;
764     else if ( IsRightToLeft( GetEditDoc().GetPos( aPaM.GetNode() ) ) )
765         eTextDirection = TextDirectionality::RightToLeft_TopToBottom;
766 
767     KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection );
768 
769     bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1();
770     sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode();
771 
772     if ( DoVisualCursorTraveling() )
773     {
774         // Only for simple cursor movement...
775         if ( !bCtrl && ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) ) )
776         {
777             aPaM = CursorVisualLeftRight( pEditView, aPaM, rKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL, rKeyEvent.GetKeyCode().GetCode() == KEY_LEFT );
778             nCode = 0;  // skip switch statement
779         }
780     }
781 
782     bool bKeyModifySelection = aTranslatedKeyEvent.GetKeyCode().IsShift();
783     switch ( nCode )
784     {
785         case KEY_UP:        aPaM = CursorUp( aPaM, pEditView );
786                             break;
787         case KEY_DOWN:      aPaM = CursorDown( aPaM, pEditView );
788                             break;
789         case KEY_LEFT:      aPaM = bCtrl ? WordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL );
790                             break;
791         case KEY_RIGHT:     aPaM = bCtrl ? WordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL );
792                             break;
793         case KEY_HOME:      aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM );
794                             break;
795         case KEY_END:       aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM );
796                             break;
797         case KEY_PAGEUP:    aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM, pEditView );
798                             break;
799         case KEY_PAGEDOWN:  aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM, pEditView );
800                             break;
801         case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
802                             aPaM = CursorStartOfLine( aPaM );
803                             bKeyModifySelection = false;
804                             break;
805         case css::awt::Key::MOVE_TO_END_OF_LINE:
806                             aPaM = CursorEndOfLine( aPaM );
807                             bKeyModifySelection = false;
808                             break;
809         case css::awt::Key::MOVE_WORD_BACKWARD:
810                             aPaM = WordLeft( aPaM );
811                             bKeyModifySelection = false;
812                             break;
813         case css::awt::Key::MOVE_WORD_FORWARD:
814                             aPaM = WordRight( aPaM );
815                             bKeyModifySelection = false;
816                             break;
817         case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
818                             aPaM = CursorStartOfParagraph( aPaM );
819                             if( aPaM == aOldPaM )
820                             {
821                                 aPaM = CursorLeft( aPaM );
822                                 aPaM = CursorStartOfParagraph( aPaM );
823                             }
824                             bKeyModifySelection = false;
825                             break;
826         case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
827                             aPaM = CursorEndOfParagraph( aPaM );
828                             if( aPaM == aOldPaM )
829                             {
830                                 aPaM = CursorRight( aPaM );
831                                 aPaM = CursorEndOfParagraph( aPaM );
832                             }
833                             bKeyModifySelection = false;
834                             break;
835         case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
836                             aPaM = CursorStartOfDoc();
837                             bKeyModifySelection = false;
838                             break;
839         case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
840                             aPaM = CursorEndOfDoc();
841                             bKeyModifySelection = false;
842                             break;
843         case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
844                             aPaM = CursorStartOfLine( aPaM );
845                             bKeyModifySelection = true;
846                             break;
847         case css::awt::Key::SELECT_TO_END_OF_LINE:
848                             aPaM = CursorEndOfLine( aPaM );
849                             bKeyModifySelection = true;
850                             break;
851         case css::awt::Key::SELECT_BACKWARD:
852                             aPaM = CursorLeft( aPaM );
853                             bKeyModifySelection = true;
854                             break;
855         case css::awt::Key::SELECT_FORWARD:
856                             aPaM = CursorRight( aPaM );
857                             bKeyModifySelection = true;
858                             break;
859         case css::awt::Key::SELECT_WORD_BACKWARD:
860                             aPaM = WordLeft( aPaM );
861                             bKeyModifySelection = true;
862                             break;
863         case css::awt::Key::SELECT_WORD_FORWARD:
864                             aPaM = WordRight( aPaM );
865                             bKeyModifySelection = true;
866                             break;
867         case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
868                             aPaM = CursorStartOfParagraph( aPaM );
869                             if( aPaM == aOldPaM )
870                             {
871                                 aPaM = CursorLeft( aPaM );
872                                 aPaM = CursorStartOfParagraph( aPaM );
873                             }
874                             bKeyModifySelection = true;
875                             break;
876         case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
877                             aPaM = CursorEndOfParagraph( aPaM );
878                             if( aPaM == aOldPaM )
879                             {
880                                 aPaM = CursorRight( aPaM );
881                                 aPaM = CursorEndOfParagraph( aPaM );
882                             }
883                             bKeyModifySelection = true;
884                             break;
885         case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
886                             aPaM = CursorStartOfDoc();
887                             bKeyModifySelection = true;
888                             break;
889         case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
890                             aPaM = CursorEndOfDoc();
891                             bKeyModifySelection = true;
892                             break;
893     }
894 
895     if ( aOldPaM != aPaM )
896     {
897         CursorMoved( aOldPaM.GetNode() );
898     }
899 
900     // May cause, a CreateAnchor or deselection all
901     aSelEngine.SetCurView( pEditView );
902     aSelEngine.CursorPosChanging( bKeyModifySelection, aTranslatedKeyEvent.GetKeyCode().IsMod1() );
903     EditPaM aOldEnd( pEditView->pImpEditView->GetEditSelection().Max() );
904 
905     {
906         EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection());
907         aNewSelection.Max() = aPaM;
908         pEditView->pImpEditView->SetEditSelection(aNewSelection);
909         // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Max()) = aPaM;
910     }
911 
912     if ( bKeyModifySelection )
913     {
914         // Then the selection is expanded ... or the whole selection is painted in case of tiled rendering.
915         EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? pEditView->pImpEditView->GetEditSelection().Min() : aOldEnd, aPaM );
916         pEditView->pImpEditView->DrawSelectionXOR( aTmpNewSel );
917     }
918     else
919     {
920         EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection());
921         aNewSelection.Min() = aPaM;
922         pEditView->pImpEditView->SetEditSelection(aNewSelection);
923         // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Min()) = aPaM;
924     }
925 
926     return pEditView->pImpEditView->GetEditSelection();
927 }
928 
929 EditPaM ImpEditEngine::CursorVisualStartEnd( EditView const * pEditView, const EditPaM& rPaM, bool bStart )
930 {
931     EditPaM aPaM( rPaM );
932 
933     sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() );
934     ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
935     if (!pParaPortion)
936         return aPaM;
937 
938     sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false );
939     const EditLine& rLine = pParaPortion->GetLines()[nLine];
940     bool bEmptyLine = rLine.GetStart() == rLine.GetEnd();
941 
942     pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE;
943 
944     if ( !bEmptyLine )
945     {
946         OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart());
947 
948         UErrorCode nError = U_ZERO_ERROR;
949         UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError );
950 
951         const UBiDiLevel  nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/;
952         ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError );
953 
954         sal_Int32 nVisPos = bStart ? 0 : aLine.getLength()-1;
955         const sal_Int32 nLogPos = ubidi_getLogicalIndex( pBidi, nVisPos, &nError );
956 
957         ubidi_close( pBidi );
958 
959         aPaM.SetIndex( nLogPos + rLine.GetStart() );
960 
961         sal_Int32 nTmp;
962         sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTmp, true );
963         const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
964         bool bPortionRTL = rTextPortion.IsRightToLeft();
965 
966         if ( bStart )
967         {
968             pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 0 : 1 );
969             // Maybe we must be *behind* the character
970             if ( bPortionRTL && pEditView->IsInsertMode() )
971                 aPaM.SetIndex( aPaM.GetIndex()+1 );
972         }
973         else
974         {
975             pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 1 : 0 );
976             if ( !bPortionRTL && pEditView->IsInsertMode() )
977                 aPaM.SetIndex( aPaM.GetIndex()+1 );
978         }
979     }
980 
981     return aPaM;
982 }
983 
984 EditPaM ImpEditEngine::CursorVisualLeftRight( EditView const * pEditView, const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode, bool bVisualToLeft )
985 {
986     EditPaM aPaM( rPaM );
987 
988     sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() );
989     ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
990     if (!pParaPortion)
991         return aPaM;
992 
993     sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false );
994     const EditLine& rLine = pParaPortion->GetLines()[nLine];
995     bool bEmptyLine = rLine.GetStart() == rLine.GetEnd();
996 
997     pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE;
998 
999     bool bParaRTL = IsRightToLeft( nPara );
1000 
1001     bool bDone = false;
1002 
1003     if ( bEmptyLine )
1004     {
1005         if ( bVisualToLeft )
1006         {
1007             aPaM = CursorUp( aPaM, pEditView );
1008             if ( aPaM != rPaM )
1009                 aPaM = CursorVisualStartEnd( pEditView, aPaM, false );
1010         }
1011         else
1012         {
1013             aPaM = CursorDown( aPaM, pEditView );
1014             if ( aPaM != rPaM )
1015                 aPaM = CursorVisualStartEnd( pEditView, aPaM, true );
1016         }
1017 
1018         bDone = true;
1019     }
1020 
1021     bool bLogicalBackward = bParaRTL ? !bVisualToLeft : bVisualToLeft;
1022 
1023     if ( !bDone && pEditView->IsInsertMode() )
1024     {
1025         // Check if we are within a portion and don't have overwrite mode, then it's easy...
1026         sal_Int32 nPortionStart;
1027         sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart );
1028         const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
1029 
1030         bool bPortionBoundary = ( aPaM.GetIndex() == nPortionStart ) || ( aPaM.GetIndex() == (nPortionStart+rTextPortion.GetLen()) );
1031         sal_uInt16 nRTLLevel = rTextPortion.GetRightToLeftLevel();
1032 
1033         // Portion boundary doesn't matter if both have same RTL level
1034         sal_Int32 nRTLLevelNextPortion = -1;
1035         if ( bPortionBoundary && aPaM.GetIndex() && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ) )
1036         {
1037             sal_Int32 nTmp;
1038             sal_Int32 nNextTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex()+1, nTmp, !bLogicalBackward );
1039             const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nNextTextPortion];
1040             nRTLLevelNextPortion = rNextTextPortion.GetRightToLeftLevel();
1041         }
1042 
1043         if ( !bPortionBoundary || ( nRTLLevel == nRTLLevelNextPortion ) )
1044         {
1045             if (bVisualToLeft != bool(nRTLLevel % 2))
1046             {
1047                 aPaM = CursorLeft( aPaM, nCharacterIteratorMode );
1048                 pEditView->pImpEditView->SetCursorBidiLevel( 1 );
1049             }
1050             else
1051             {
1052                 aPaM = CursorRight( aPaM, nCharacterIteratorMode );
1053                 pEditView->pImpEditView->SetCursorBidiLevel( 0 );
1054             }
1055             bDone = true;
1056         }
1057     }
1058 
1059     if ( !bDone )
1060     {
1061         bool bGotoStartOfNextLine = false;
1062         bool bGotoEndOfPrevLine = false;
1063 
1064         OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart());
1065         const sal_Int32 nPosInLine = aPaM.GetIndex() - rLine.GetStart();
1066 
1067         UErrorCode nError = U_ZERO_ERROR;
1068         UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError );
1069 
1070         const UBiDiLevel  nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/;
1071         ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError );
1072 
1073         if ( !pEditView->IsInsertMode() )
1074         {
1075             bool bEndOfLine = nPosInLine == aLine.getLength();
1076             sal_Int32 nVisPos = ubidi_getVisualIndex( pBidi, !bEndOfLine ? nPosInLine : nPosInLine-1, &nError );
1077             if ( bVisualToLeft )
1078             {
1079                 bGotoEndOfPrevLine = nVisPos == 0;
1080                 if ( !bEndOfLine )
1081                     nVisPos--;
1082             }
1083             else
1084             {
1085                 bGotoStartOfNextLine = nVisPos == (aLine.getLength() - 1);
1086                 if ( !bEndOfLine )
1087                     nVisPos++;
1088             }
1089 
1090             if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine )
1091             {
1092                 aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) );
1093                 pEditView->pImpEditView->SetCursorBidiLevel( 0 );
1094             }
1095         }
1096         else
1097         {
1098             bool bWasBehind = false;
1099             bool bBeforePortion = !nPosInLine || pEditView->pImpEditView->GetCursorBidiLevel() == 1;
1100             if ( nPosInLine && ( !bBeforePortion ) ) // before the next portion
1101                 bWasBehind = true;  // step one back, otherwise visual will be unusable when rtl portion follows.
1102 
1103             sal_Int32 nPortionStart;
1104             sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, bBeforePortion );
1105             const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
1106             bool bRTLPortion = rTextPortion.IsRightToLeft();
1107 
1108             // -1: We are 'behind' the character
1109             long nVisPos = static_cast<long>(ubidi_getVisualIndex( pBidi, bWasBehind ? nPosInLine-1 : nPosInLine, &nError ));
1110             if ( bVisualToLeft )
1111             {
1112                 if ( !bWasBehind || bRTLPortion )
1113                     nVisPos--;
1114             }
1115             else
1116             {
1117                 if ( bWasBehind || bRTLPortion || bBeforePortion )
1118                     nVisPos++;
1119             }
1120 
1121             bGotoEndOfPrevLine = nVisPos < 0;
1122             bGotoStartOfNextLine = nVisPos >= aLine.getLength();
1123 
1124             if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine )
1125             {
1126                 aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) );
1127 
1128                 // RTL portion, stay visually on the left side.
1129                 sal_Int32 _nPortionStart;
1130                 // sal_uInt16 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, !bRTLPortion );
1131                 sal_Int32 _nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), _nPortionStart, true );
1132                 const TextPortion& _rTextPortion = pParaPortion->GetTextPortions()[_nTextPortion];
1133                 if ( bVisualToLeft && !bRTLPortion && _rTextPortion.IsRightToLeft() )
1134                     aPaM.SetIndex( aPaM.GetIndex()+1 );
1135                 else if ( !bVisualToLeft && bRTLPortion && ( bWasBehind || !_rTextPortion.IsRightToLeft() ) )
1136                     aPaM.SetIndex( aPaM.GetIndex()+1 );
1137 
1138                 pEditView->pImpEditView->SetCursorBidiLevel( _nPortionStart );
1139             }
1140         }
1141 
1142         ubidi_close( pBidi );
1143 
1144         if ( bGotoEndOfPrevLine )
1145         {
1146             aPaM = CursorUp( aPaM, pEditView );
1147             if ( aPaM != rPaM )
1148                 aPaM = CursorVisualStartEnd( pEditView, aPaM, false );
1149         }
1150         else if ( bGotoStartOfNextLine )
1151         {
1152             aPaM = CursorDown( aPaM, pEditView );
1153             if ( aPaM != rPaM )
1154                 aPaM = CursorVisualStartEnd( pEditView, aPaM, true );
1155         }
1156     }
1157     return aPaM;
1158 }
1159 
1160 
1161 EditPaM ImpEditEngine::CursorLeft( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
1162 {
1163     EditPaM aCurPaM( rPaM );
1164     EditPaM aNewPaM( aCurPaM );
1165 
1166     if ( aCurPaM.GetIndex() )
1167     {
1168         sal_Int32 nCount = 1;
1169         uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1170         aNewPaM.SetIndex(
1171              _xBI->previousCharacters(
1172                  aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount));
1173     }
1174     else
1175     {
1176         ContentNode* pNode = aCurPaM.GetNode();
1177         pNode = GetPrevVisNode( pNode );
1178         if ( pNode )
1179         {
1180             aNewPaM.SetNode( pNode );
1181             aNewPaM.SetIndex( pNode->Len() );
1182         }
1183     }
1184 
1185     return aNewPaM;
1186 }
1187 
1188 EditPaM ImpEditEngine::CursorRight( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
1189 {
1190     EditPaM aCurPaM( rPaM );
1191     EditPaM aNewPaM( aCurPaM );
1192 
1193     if ( aCurPaM.GetIndex() < aCurPaM.GetNode()->Len() )
1194     {
1195         uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1196         sal_Int32 nCount = 1;
1197         aNewPaM.SetIndex(
1198             _xBI->nextCharacters(
1199                 aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount));
1200     }
1201     else
1202     {
1203         ContentNode* pNode = aCurPaM.GetNode();
1204         pNode = GetNextVisNode( pNode );
1205         if ( pNode )
1206         {
1207             aNewPaM.SetNode( pNode );
1208             aNewPaM.SetIndex( 0 );
1209         }
1210     }
1211 
1212     return aNewPaM;
1213 }
1214 
1215 EditPaM ImpEditEngine::CursorUp( const EditPaM& rPaM, EditView const * pView )
1216 {
1217     assert(pView && "No View - No Cursor Movement!");
1218 
1219     const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() );
1220     OSL_ENSURE( pPPortion, "No matching portion found: CursorUp ");
1221     sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() );
1222     const EditLine& rLine = pPPortion->GetLines()[nLine];
1223 
1224     long nX;
1225     if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW )
1226     {
1227         nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() );
1228         pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef;
1229     }
1230     else
1231         nX = pView->pImpEditView->nTravelXPos;
1232 
1233     EditPaM aNewPaM( rPaM );
1234     if ( nLine )    // same paragraph
1235     {
1236         const EditLine& rPrevLine = pPPortion->GetLines()[nLine-1];
1237         aNewPaM.SetIndex( GetChar( pPPortion, &rPrevLine, nX ) );
1238         // If a previous automatically wrapped line, and one has to be exactly
1239         // at the end of this line, the cursor lands on the current line at the
1240         // beginning. See Problem: Last character of an automatically wrapped
1241         // Row = cursor
1242         if ( aNewPaM.GetIndex() && ( aNewPaM.GetIndex() == rLine.GetStart() ) )
1243             aNewPaM = CursorLeft( aNewPaM );
1244     }
1245     else    // previous paragraph
1246     {
1247         const ParaPortion* pPrevPortion = GetPrevVisPortion( pPPortion );
1248         if ( pPrevPortion )
1249         {
1250             const EditLine& rLine2 = pPrevPortion->GetLines()[pPrevPortion->GetLines().Count()-1];
1251             aNewPaM.SetNode( pPrevPortion->GetNode() );
1252             aNewPaM.SetIndex( GetChar( pPrevPortion, &rLine2, nX+nOnePixelInRef ) );
1253         }
1254     }
1255 
1256     return aNewPaM;
1257 }
1258 
1259 EditPaM ImpEditEngine::CursorDown( const EditPaM& rPaM, EditView const * pView )
1260 {
1261     OSL_ENSURE( pView, "No View - No Cursor Movement!" );
1262 
1263     const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() );
1264     OSL_ENSURE( pPPortion, "No matching portion found: CursorDown" );
1265     sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() );
1266 
1267     long nX;
1268     if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW )
1269     {
1270         const EditLine& rLine = pPPortion->GetLines()[nLine];
1271         nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() );
1272         pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef;
1273     }
1274     else
1275         nX = pView->pImpEditView->nTravelXPos;
1276 
1277     EditPaM aNewPaM( rPaM );
1278     if ( nLine < pPPortion->GetLines().Count()-1 )
1279     {
1280         const EditLine& rNextLine = pPPortion->GetLines()[nLine+1];
1281         aNewPaM.SetIndex( GetChar( pPPortion, &rNextLine, nX ) );
1282         // Special treatment, see CursorUp ...
1283         if ( ( aNewPaM.GetIndex() == rNextLine.GetEnd() ) && ( aNewPaM.GetIndex() > rNextLine.GetStart() ) && ( aNewPaM.GetIndex() < pPPortion->GetNode()->Len() ) )
1284             aNewPaM = CursorLeft( aNewPaM );
1285     }
1286     else    // next paragraph
1287     {
1288         const ParaPortion* pNextPortion = GetNextVisPortion( pPPortion );
1289         if ( pNextPortion )
1290         {
1291             const EditLine& rLine = pNextPortion->GetLines()[0];
1292             aNewPaM.SetNode( pNextPortion->GetNode() );
1293             // Never at the very end when several lines, because then a line
1294             // below the cursor appears.
1295             aNewPaM.SetIndex( GetChar( pNextPortion, &rLine, nX+nOnePixelInRef ) );
1296             if ( ( aNewPaM.GetIndex() == rLine.GetEnd() ) && ( aNewPaM.GetIndex() > rLine.GetStart() ) && ( pNextPortion->GetLines().Count() > 1 ) )
1297                 aNewPaM = CursorLeft( aNewPaM );
1298         }
1299     }
1300 
1301     return aNewPaM;
1302 }
1303 
1304 EditPaM ImpEditEngine::CursorStartOfLine( const EditPaM& rPaM )
1305 {
1306     const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() );
1307     OSL_ENSURE( pCurPortion, "No Portion for the PaM ?" );
1308     sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() );
1309     const EditLine& rLine = pCurPortion->GetLines()[nLine];
1310 
1311     EditPaM aNewPaM( rPaM );
1312     aNewPaM.SetIndex( rLine.GetStart() );
1313     return aNewPaM;
1314 }
1315 
1316 EditPaM ImpEditEngine::CursorEndOfLine( const EditPaM& rPaM )
1317 {
1318     const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() );
1319     OSL_ENSURE( pCurPortion, "No Portion for the PaM ?" );
1320     sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() );
1321     const EditLine& rLine = pCurPortion->GetLines()[nLine];
1322 
1323     EditPaM aNewPaM( rPaM );
1324     aNewPaM.SetIndex( rLine.GetEnd() );
1325     if ( rLine.GetEnd() > rLine.GetStart() )
1326     {
1327         if ( aNewPaM.GetNode()->IsFeature( aNewPaM.GetIndex() - 1 ) )
1328         {
1329             // When a soft break, be in front of it!
1330             const EditCharAttrib* pNextFeature = aNewPaM.GetNode()->GetCharAttribs().FindFeature( aNewPaM.GetIndex()-1 );
1331             if ( pNextFeature && ( pNextFeature->GetItem()->Which() == EE_FEATURE_LINEBR ) )
1332                 aNewPaM = CursorLeft( aNewPaM );
1333         }
1334         else if ( ( aNewPaM.GetNode()->GetChar( aNewPaM.GetIndex() - 1 ) == ' ' ) && ( aNewPaM.GetIndex() != aNewPaM.GetNode()->Len() ) )
1335         {
1336             // For a Blank in an auto wrapped line, it makes sense, to stand
1337             // in front of it, since the user wants to be after the word.
1338             // If this is changed, special treatment for Pos1 to End!
1339             aNewPaM = CursorLeft( aNewPaM );
1340         }
1341     }
1342     return aNewPaM;
1343 }
1344 
1345 EditPaM ImpEditEngine::CursorStartOfParagraph( const EditPaM& rPaM )
1346 {
1347     EditPaM aPaM(rPaM);
1348     aPaM.SetIndex(0);
1349     return aPaM;
1350 }
1351 
1352 EditPaM ImpEditEngine::CursorEndOfParagraph( const EditPaM& rPaM )
1353 {
1354     EditPaM aPaM(rPaM);
1355     aPaM.SetIndex(rPaM.GetNode()->Len());
1356     return aPaM;
1357 }
1358 
1359 EditPaM ImpEditEngine::CursorStartOfDoc()
1360 {
1361     EditPaM aPaM( aEditDoc.GetObject( 0 ), 0 );
1362     return aPaM;
1363 }
1364 
1365 EditPaM ImpEditEngine::CursorEndOfDoc()
1366 {
1367     ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 );
1368     ParaPortion* pLastPortion = GetParaPortions().SafeGetObject( aEditDoc.Count()-1 );
1369     OSL_ENSURE( pLastNode && pLastPortion, "CursorEndOfDoc: Node or Portion not found" );
1370     if (!(pLastNode && pLastPortion))
1371         return EditPaM();
1372 
1373     if ( !pLastPortion->IsVisible() )
1374     {
1375         pLastNode = GetPrevVisNode( pLastPortion->GetNode() );
1376         OSL_ENSURE( pLastNode, "No visible paragraph?" );
1377         if ( !pLastNode )
1378             pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 );
1379     }
1380 
1381     EditPaM aPaM( pLastNode, pLastNode->Len() );
1382     return aPaM;
1383 }
1384 
1385 EditPaM ImpEditEngine::PageUp( const EditPaM& rPaM, EditView const * pView )
1386 {
1387     tools::Rectangle aRect = PaMtoEditCursor( rPaM );
1388     Point aTopLeft = aRect.TopLeft();
1389     aTopLeft.AdjustY( -(pView->GetVisArea().GetHeight() *9/10) );
1390     aTopLeft.AdjustX(nOnePixelInRef );
1391     if ( aTopLeft.Y() < 0 )
1392     {
1393         aTopLeft.setY( 0 );
1394     }
1395     return GetPaM( aTopLeft );
1396 }
1397 
1398 EditPaM ImpEditEngine::PageDown( const EditPaM& rPaM, EditView const * pView )
1399 {
1400     tools::Rectangle aRect = PaMtoEditCursor( rPaM );
1401     Point aBottomRight = aRect.BottomRight();
1402     aBottomRight.AdjustY(pView->GetVisArea().GetHeight() *9/10 );
1403     aBottomRight.AdjustX(nOnePixelInRef );
1404     long nHeight = GetTextHeight();
1405     if ( aBottomRight.Y() > nHeight )
1406     {
1407         aBottomRight.setY( nHeight-2 );
1408     }
1409     return GetPaM( aBottomRight );
1410 }
1411 
1412 EditPaM ImpEditEngine::WordLeft( const EditPaM& rPaM )
1413 {
1414     const sal_Int32 nCurrentPos = rPaM.GetIndex();
1415     EditPaM aNewPaM( rPaM );
1416     if ( nCurrentPos == 0 )
1417     {
1418         // Previous paragraph...
1419         sal_Int32 nCurPara = aEditDoc.GetPos( aNewPaM.GetNode() );
1420         ContentNode* pPrevNode = aEditDoc.GetObject( --nCurPara );
1421         if ( pPrevNode )
1422         {
1423             aNewPaM.SetNode( pPrevNode );
1424             aNewPaM.SetIndex( pPrevNode->Len() );
1425         }
1426     }
1427     else
1428     {
1429         // we need to increase the position by 1 when retrieving the locale
1430         // since the attribute for the char left to the cursor position is returned
1431         EditPaM aTmpPaM( aNewPaM );
1432         if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
1433             aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
1434         lang::Locale aLocale( GetLocale( aTmpPaM ) );
1435 
1436         uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1437         i18n::Boundary aBoundary =
1438             _xBI->getWordBoundary(aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true);
1439         if ( aBoundary.startPos >= nCurrentPos )
1440             aBoundary = _xBI->previousWord(
1441                 aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES);
1442         aNewPaM.SetIndex( ( aBoundary.startPos != -1 ) ? aBoundary.startPos : 0 );
1443     }
1444 
1445     return aNewPaM;
1446 }
1447 
1448 EditPaM ImpEditEngine::WordRight( const EditPaM& rPaM, sal_Int16 nWordType )
1449 {
1450     const sal_Int32 nMax = rPaM.GetNode()->Len();
1451     EditPaM aNewPaM( rPaM );
1452     if ( aNewPaM.GetIndex() < nMax )
1453     {
1454         // we need to increase the position by 1 when retrieving the locale
1455         // since the attribute for the char left to the cursor position is returned
1456         EditPaM aTmpPaM( aNewPaM );
1457         aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
1458         lang::Locale aLocale( GetLocale( aTmpPaM ) );
1459 
1460         uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1461         i18n::Boundary aBoundary = _xBI->nextWord(
1462             aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), aLocale, nWordType);
1463         aNewPaM.SetIndex( aBoundary.startPos );
1464     }
1465     // not 'else', maybe the index reached nMax now...
1466     if ( aNewPaM.GetIndex() >= nMax )
1467     {
1468         // Next paragraph ...
1469         sal_Int32 nCurPara = aEditDoc.GetPos( aNewPaM.GetNode() );
1470         ContentNode* pNextNode = aEditDoc.GetObject( ++nCurPara );
1471         if ( pNextNode )
1472         {
1473             aNewPaM.SetNode( pNextNode );
1474             aNewPaM.SetIndex( 0 );
1475         }
1476     }
1477     return aNewPaM;
1478 }
1479 
1480 EditPaM ImpEditEngine::StartOfWord( const EditPaM& rPaM )
1481 {
1482     EditPaM aNewPaM( rPaM );
1483 
1484     // we need to increase the position by 1 when retrieving the locale
1485     // since the attribute for the char left to the cursor position is returned
1486     EditPaM aTmpPaM( aNewPaM );
1487     if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
1488         aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
1489     lang::Locale aLocale( GetLocale( aTmpPaM ) );
1490 
1491     uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1492     i18n::Boundary aBoundary = _xBI->getWordBoundary(
1493         rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true);
1494 
1495     aNewPaM.SetIndex( aBoundary.startPos );
1496     return aNewPaM;
1497 }
1498 
1499 EditPaM ImpEditEngine::EndOfWord( const EditPaM& rPaM )
1500 {
1501     EditPaM aNewPaM( rPaM );
1502 
1503     // we need to increase the position by 1 when retrieving the locale
1504     // since the attribute for the char left to the cursor position is returned
1505     EditPaM aTmpPaM( aNewPaM );
1506     if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
1507         aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
1508     lang::Locale aLocale( GetLocale( aTmpPaM ) );
1509 
1510     uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1511     i18n::Boundary aBoundary = _xBI->getWordBoundary(
1512         rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true);
1513 
1514     aNewPaM.SetIndex( aBoundary.endPos );
1515     return aNewPaM;
1516 }
1517 
1518 EditSelection ImpEditEngine::SelectWord( const EditSelection& rCurSel, sal_Int16 nWordType, bool bAcceptStartOfWord )
1519 {
1520     EditSelection aNewSel( rCurSel );
1521     EditPaM aPaM( rCurSel.Max() );
1522 
1523     // we need to increase the position by 1 when retrieving the locale
1524     // since the attribute for the char left to the cursor position is returned
1525     EditPaM aTmpPaM( aPaM );
1526     if ( aTmpPaM.GetIndex() < aPaM.GetNode()->Len() )
1527         aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
1528     lang::Locale aLocale( GetLocale( aTmpPaM ) );
1529 
1530     uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1531     sal_Int16 nType = _xBI->getWordType(
1532         aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale);
1533 
1534     if ( nType == i18n::WordType::ANY_WORD )
1535     {
1536         i18n::Boundary aBoundary = _xBI->getWordBoundary(
1537             aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale, nWordType, true);
1538 
1539         // don't select when cursor at end of word
1540         if ( ( aBoundary.endPos > aPaM.GetIndex() ) &&
1541              ( ( aBoundary.startPos < aPaM.GetIndex() ) || ( bAcceptStartOfWord && ( aBoundary.startPos == aPaM.GetIndex() ) ) ) )
1542         {
1543             aNewSel.Min().SetIndex( aBoundary.startPos );
1544             aNewSel.Max().SetIndex( aBoundary.endPos );
1545         }
1546     }
1547 
1548     return aNewSel;
1549 }
1550 
1551 EditSelection ImpEditEngine::SelectSentence( const EditSelection& rCurSel )
1552     const
1553 {
1554     uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1555     const EditPaM& rPaM = rCurSel.Min();
1556     const ContentNode* pNode = rPaM.GetNode();
1557     // #i50710# line breaks are marked with 0x01 - the break iterator prefers 0x0a for that
1558     const OUString sParagraph = pNode->GetString().replaceAll("\x01", "\x0a");
1559     //return Null if search starts at the beginning of the string
1560     sal_Int32 nStart = rPaM.GetIndex() ? _xBI->beginOfSentence( sParagraph, rPaM.GetIndex(), GetLocale( rPaM ) ) : 0;
1561 
1562     sal_Int32 nEnd = _xBI->endOfSentence(
1563         pNode->GetString(), rPaM.GetIndex(), GetLocale(rPaM));
1564 
1565     EditSelection aNewSel( rCurSel );
1566     OSL_ENSURE(pNode->Len() ? (nStart < pNode->Len()) : (nStart == 0), "sentence start index out of range");
1567     OSL_ENSURE(nEnd <= pNode->Len(), "sentence end index out of range");
1568     aNewSel.Min().SetIndex( nStart );
1569     aNewSel.Max().SetIndex( nEnd );
1570     return aNewSel;
1571 }
1572 
1573 bool ImpEditEngine::IsInputSequenceCheckingRequired( sal_Unicode nChar, const EditSelection& rCurSel ) const
1574 {
1575     uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1576     if (!pCTLOptions)
1577         pCTLOptions.reset( new SvtCTLOptions );
1578 
1579     // get the index that really is first
1580     const sal_Int32 nFirstPos = std::min(rCurSel.Min().GetIndex(), rCurSel.Max().GetIndex());
1581 
1582     bool bIsSequenceChecking =
1583         pCTLOptions->IsCTLFontEnabled() &&
1584         pCTLOptions->IsCTLSequenceChecking() &&
1585         nFirstPos != 0 && /* first char needs not to be checked */
1586         _xBI.is() && i18n::ScriptType::COMPLEX == _xBI->getScriptType( OUString( nChar ), 0 );
1587 
1588     return bIsSequenceChecking;
1589 }
1590 
1591 static  bool lcl_HasStrongLTR ( const OUString& rTxt, sal_Int32 nStart, sal_Int32 nEnd )
1592  {
1593      for( sal_Int32 nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx )
1594      {
1595          const UCharDirection nCharDir = u_charDirection ( rTxt[ nCharIdx ] );
1596          if ( nCharDir == U_LEFT_TO_RIGHT ||
1597               nCharDir == U_LEFT_TO_RIGHT_EMBEDDING ||
1598               nCharDir == U_LEFT_TO_RIGHT_OVERRIDE )
1599              return true;
1600      }
1601      return false;
1602  }
1603 
1604 
1605 void ImpEditEngine::InitScriptTypes( sal_Int32 nPara )
1606 {
1607     ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
1608     if (!pParaPortion)
1609         return;
1610 
1611     ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
1612     rTypes.clear();
1613 
1614     ContentNode* pNode = pParaPortion->GetNode();
1615     if ( pNode->Len() )
1616     {
1617         uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1618 
1619         OUString aText = pNode->GetString();
1620 
1621         // To handle fields put the character from the field in the string,
1622         // because endOfScript( ... ) will skip the CH_FEATURE, because this is WEAK
1623         const EditCharAttrib* pField = pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, 0 );
1624         while ( pField )
1625         {
1626             const OUString aFldText = static_cast<const EditCharAttribField*>(pField)->GetFieldValue();
1627             if ( !aFldText.isEmpty() )
1628             {
1629                 aText = aText.replaceAt( pField->GetStart(), 1, aFldText.copy(0,1) );
1630                 short nFldScriptType = _xBI->getScriptType( aFldText, 0 );
1631 
1632                 for ( sal_Int32 nCharInField = 1; nCharInField < aFldText.getLength(); nCharInField++ )
1633                 {
1634                     short nTmpType = _xBI->getScriptType( aFldText, nCharInField );
1635 
1636                     // First char from field wins...
1637                     if ( nFldScriptType == i18n::ScriptType::WEAK )
1638                     {
1639                         nFldScriptType = nTmpType;
1640                         aText = aText.replaceAt( pField->GetStart(), 1, aFldText.copy(nCharInField,1) );
1641                     }
1642 
1643                     // ...  but if the first one is LATIN, and there are CJK or CTL chars too,
1644                     // we prefer that ScriptType because we need another font.
1645                     if ( ( nTmpType == i18n::ScriptType::ASIAN ) || ( nTmpType == i18n::ScriptType::COMPLEX ) )
1646                     {
1647                         aText = aText.replaceAt( pField->GetStart(), 1, aFldText.copy(nCharInField,1) );
1648                         break;
1649                     }
1650                 }
1651             }
1652             // #112831# Last Field might go from 0xffff to 0x0000
1653             pField = pField->GetEnd() ? pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, pField->GetEnd() ) : nullptr;
1654         }
1655 
1656         sal_Int32 nTextLen = aText.getLength();
1657 
1658         sal_Int32 nPos = 0;
1659         short nScriptType = _xBI->getScriptType( aText, nPos );
1660         rTypes.emplace_back( nScriptType, nPos, nTextLen );
1661         nPos = _xBI->endOfScript( aText, nPos, nScriptType );
1662         while ( ( nPos != -1 ) && ( nPos < nTextLen ) )
1663         {
1664             rTypes.back().nEndPos = nPos;
1665 
1666             nScriptType = _xBI->getScriptType( aText, nPos );
1667             long nEndPos = _xBI->endOfScript( aText, nPos, nScriptType );
1668 
1669             if ( ( nScriptType == i18n::ScriptType::WEAK ) || ( nScriptType == rTypes.back().nScriptType ) )
1670             {
1671                 // Expand last ScriptTypePosInfo, don't create weak or unnecessary portions
1672                 rTypes.back().nEndPos = nEndPos;
1673             }
1674             else
1675             {
1676                 if ( _xBI->getScriptType( aText, nPos - 1 ) == i18n::ScriptType::WEAK )
1677                 {
1678                     switch ( u_charType(aText.iterateCodePoints(&nPos, 0) ) ) {
1679                     case U_NON_SPACING_MARK:
1680                     case U_ENCLOSING_MARK:
1681                     case U_COMBINING_SPACING_MARK:
1682                         --nPos;
1683                         rTypes.back().nEndPos--;
1684                         break;
1685                     }
1686                 }
1687                 rTypes.emplace_back( nScriptType, nPos, nTextLen );
1688             }
1689 
1690             nPos = nEndPos;
1691         }
1692 
1693         if ( rTypes[0].nScriptType == i18n::ScriptType::WEAK )
1694             rTypes[0].nScriptType = ( rTypes.size() > 1 ) ? rTypes[1].nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() );
1695 
1696         // create writing direction information:
1697         if ( pParaPortion->aWritingDirectionInfos.empty() )
1698             InitWritingDirections( nPara );
1699 
1700         // i89825: Use CTL font for numbers embedded into an RTL run:
1701         WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos;
1702         for (WritingDirectionInfo & rDirInfo : rDirInfos)
1703         {
1704             const sal_Int32 nStart = rDirInfo.nStartPos;
1705             const sal_Int32 nEnd   = rDirInfo.nEndPos;
1706             const sal_uInt8 nCurrDirType = rDirInfo.nType;
1707 
1708             if ( nCurrDirType % 2 == UBIDI_RTL  || // text in RTL run
1709                 ( nCurrDirType > UBIDI_LTR && !lcl_HasStrongLTR( aText, nStart, nEnd ) ) ) // non-strong text in embedded LTR run
1710             {
1711                 size_t nIdx = 0;
1712 
1713                 // Skip entries in ScriptArray which are not inside the RTL run:
1714                 while ( nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart )
1715                     ++nIdx;
1716 
1717                 // Remove any entries *inside* the current run:
1718                 while (nIdx < rTypes.size() && rTypes[nIdx].nEndPos <= nEnd)
1719                 {
1720                     // coverity[use_iterator] - we're protected from a bad iterator by the above condition
1721                     rTypes.erase(rTypes.begin() + nIdx);
1722                 }
1723 
1724                 // special case:
1725                 if(nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart && rTypes[nIdx].nEndPos > nEnd)
1726                 {
1727                     rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( rTypes[nIdx].nScriptType, nEnd, rTypes[nIdx].nEndPos ) );
1728                     rTypes[nIdx].nEndPos = nStart;
1729                 }
1730 
1731                 if( nIdx )
1732                     rTypes[nIdx - 1].nEndPos = nStart;
1733 
1734                 rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( i18n::ScriptType::COMPLEX, nStart, nEnd) );
1735                 ++nIdx;
1736 
1737                 if( nIdx < rTypes.size() )
1738                     rTypes[nIdx].nStartPos = nEnd;
1739             }
1740         }
1741     }
1742 }
1743 
1744 namespace {
1745 
1746 struct FindByPos
1747 {
1748     explicit FindByPos(sal_Int32 nPos)
1749         : mnPos(nPos)
1750     {
1751     }
1752 
1753     bool operator()(const ScriptTypePosInfos::value_type& rValue)
1754     {
1755         return rValue.nStartPos <= mnPos && rValue.nEndPos >= mnPos;
1756     }
1757 
1758 private:
1759     sal_Int32 mnPos;
1760 };
1761 
1762 }
1763 
1764 sal_uInt16 ImpEditEngine::GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEndPos ) const
1765 {
1766     sal_uInt16 nScriptType = 0;
1767 
1768     if ( pEndPos )
1769         *pEndPos = rPaM.GetNode()->Len();
1770 
1771     if ( rPaM.GetNode()->Len() )
1772     {
1773         sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() );
1774         const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
1775         if (pParaPortion)
1776         {
1777             if ( pParaPortion->aScriptInfos.empty() )
1778                 const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
1779 
1780             const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
1781 
1782             const sal_Int32 nPos = rPaM.GetIndex();
1783             ScriptTypePosInfos::const_iterator itr = std::find_if(rTypes.begin(), rTypes.end(), FindByPos(nPos));
1784             if(itr != rTypes.end())
1785             {
1786                 nScriptType = itr->nScriptType;
1787                 if( pEndPos )
1788                     *pEndPos = itr->nEndPos;
1789             }
1790         }
1791     }
1792     return nScriptType ? nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() );
1793 }
1794 
1795 SvtScriptType ImpEditEngine::GetItemScriptType( const EditSelection& rSel ) const
1796 {
1797     EditSelection aSel( rSel );
1798     aSel.Adjust( aEditDoc );
1799 
1800     SvtScriptType nScriptType = SvtScriptType::NONE;
1801 
1802     sal_Int32 nStartPara = GetEditDoc().GetPos( aSel.Min().GetNode() );
1803     sal_Int32 nEndPara = GetEditDoc().GetPos( aSel.Max().GetNode() );
1804 
1805     for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ )
1806     {
1807         const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
1808         if (!pParaPortion)
1809             continue;
1810 
1811         if ( pParaPortion->aScriptInfos.empty() )
1812             const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
1813 
1814         const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
1815 
1816         // find all the scripts of this range
1817         sal_Int32 nS = ( nPara == nStartPara ) ? aSel.Min().GetIndex() : 0;
1818         sal_Int32 nE = ( nPara == nEndPara ) ? aSel.Max().GetIndex() : pParaPortion->GetNode()->Len();
1819 
1820         //no selection, just bare cursor
1821         if (nStartPara == nEndPara && nS == nE)
1822         {
1823             //If we are not at the start of the paragraph we want the properties of the
1824             //preceding character. Otherwise get the properties of the next (or what the
1825             //next would have if it existed)
1826             if (nS != 0)
1827                 --nS;
1828             else
1829                 ++nE;
1830         }
1831 
1832         for (const ScriptTypePosInfo & rType : rTypes)
1833         {
1834             bool bStartInRange = rType.nStartPos <= nS && nS < rType.nEndPos;
1835             bool bEndInRange = rType.nStartPos < nE && nE <= rType.nEndPos;
1836 
1837             if (bStartInRange || bEndInRange)
1838             {
1839                 if ( rType.nScriptType != i18n::ScriptType::WEAK )
1840                     nScriptType |= SvtLanguageOptions::FromI18NToSvtScriptType( rType.nScriptType );
1841             }
1842         }
1843     }
1844     return bool(nScriptType) ? nScriptType : SvtLanguageOptions::GetScriptTypeOfLanguage( GetDefaultLanguage() );
1845 }
1846 
1847 bool ImpEditEngine::IsScriptChange( const EditPaM& rPaM ) const
1848 {
1849     bool bScriptChange = false;
1850 
1851     if ( rPaM.GetNode()->Len() )
1852     {
1853         sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() );
1854         const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
1855         if (pParaPortion)
1856         {
1857             if ( pParaPortion->aScriptInfos.empty() )
1858                 const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
1859 
1860             const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
1861             const sal_Int32 nPos = rPaM.GetIndex();
1862             for (const ScriptTypePosInfo & rType : rTypes)
1863             {
1864                 if ( rType.nStartPos == nPos )
1865                 {
1866                     bScriptChange = true;
1867                     break;
1868                 }
1869             }
1870         }
1871     }
1872     return bScriptChange;
1873 }
1874 
1875 bool ImpEditEngine::HasScriptType( sal_Int32 nPara, sal_uInt16 nType ) const
1876 {
1877     bool bTypeFound = false;
1878 
1879     const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
1880     if (pParaPortion)
1881     {
1882         if ( pParaPortion->aScriptInfos.empty() )
1883             const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
1884 
1885         const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
1886         for ( size_t n = rTypes.size(); n && !bTypeFound; )
1887         {
1888             if ( rTypes[--n].nScriptType == nType )
1889                 bTypeFound = true;
1890         }
1891     }
1892     return bTypeFound;
1893 }
1894 
1895 void ImpEditEngine::InitWritingDirections( sal_Int32 nPara )
1896 {
1897     ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
1898     if (!pParaPortion)
1899         return;
1900 
1901     WritingDirectionInfos& rInfos = pParaPortion->aWritingDirectionInfos;
1902     rInfos.clear();
1903 
1904     if (pParaPortion->GetNode()->Len())
1905     {
1906         const OUString aText = pParaPortion->GetNode()->GetString();
1907 
1908         // Bidi functions from icu 2.0
1909 
1910         UErrorCode nError = U_ZERO_ERROR;
1911         UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError );
1912         nError = U_ZERO_ERROR;
1913 
1914         const UBiDiLevel nBidiLevel = IsRightToLeft(nPara) ? 1 /*RTL*/ : 0 /*LTR*/;
1915         ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError );
1916         nError = U_ZERO_ERROR;
1917 
1918         int32_t nCount = ubidi_countRuns( pBidi, &nError );
1919 
1920         /* ubidi_countRuns can return -1 in case of error */
1921         if (nCount > 0)
1922         {
1923             int32_t nStart = 0;
1924             int32_t nEnd;
1925             UBiDiLevel nCurrDir;
1926 
1927             for (int32_t nIdx = 0; nIdx < nCount; ++nIdx)
1928             {
1929                 ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
1930                 rInfos.emplace_back( nCurrDir, nStart, nEnd );
1931                 nStart = nEnd;
1932             }
1933         }
1934 
1935         ubidi_close( pBidi );
1936     }
1937 
1938     // No infos mean ubidi error, default to LTR
1939     if ( rInfos.empty() )
1940         rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->Len() );
1941 
1942 }
1943 
1944 bool ImpEditEngine::IsRightToLeft( sal_Int32 nPara ) const
1945 {
1946     bool bR2L = false;
1947     const SvxFrameDirectionItem* pFrameDirItem = nullptr;
1948 
1949     if ( !IsVertical() )
1950     {
1951         bR2L = GetDefaultHorizontalTextDirection() == EEHorizontalTextDirection::R2L;
1952         pFrameDirItem = &GetParaAttrib( nPara, EE_PARA_WRITINGDIR );
1953         if ( pFrameDirItem->GetValue() == SvxFrameDirection::Environment )
1954         {
1955             // #103045# if DefaultHorizontalTextDirection is set, use that value, otherwise pool default.
1956             if ( GetDefaultHorizontalTextDirection() != EEHorizontalTextDirection::Default )
1957             {
1958                 pFrameDirItem = nullptr; // bR2L already set to default horizontal text direction
1959             }
1960             else
1961             {
1962                 // Use pool default
1963                 pFrameDirItem = &const_cast<ImpEditEngine*>(this)->GetEmptyItemSet().Get( EE_PARA_WRITINGDIR );
1964             }
1965         }
1966     }
1967 
1968     if ( pFrameDirItem )
1969         bR2L = pFrameDirItem->GetValue() == SvxFrameDirection::Horizontal_RL_TB;
1970 
1971     return bR2L;
1972 }
1973 
1974 bool ImpEditEngine::HasDifferentRTLLevels( const ContentNode* pNode )
1975 {
1976     bool bHasDifferentRTLLevels = false;
1977 
1978     sal_Int32 nPara = GetEditDoc().GetPos( pNode );
1979     ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
1980     if (pParaPortion)
1981     {
1982         sal_uInt16 nRTLLevel = IsRightToLeft( nPara ) ? 1 : 0;
1983         for ( sal_Int32 n = 0; n < pParaPortion->GetTextPortions().Count(); n++ )
1984         {
1985             const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n];
1986             if ( rTextPortion.GetRightToLeftLevel() != nRTLLevel )
1987             {
1988                 bHasDifferentRTLLevels = true;
1989                 break;
1990             }
1991         }
1992     }
1993     return bHasDifferentRTLLevels;
1994 }
1995 
1996 
1997 sal_uInt8 ImpEditEngine::GetRightToLeft( sal_Int32 nPara, sal_Int32 nPos, sal_Int32* pStart, sal_Int32* pEnd )
1998 {
1999     sal_uInt8 nRightToLeft = 0;
2000 
2001     ContentNode* pNode = aEditDoc.GetObject( nPara );
2002     if ( pNode && pNode->Len() )
2003     {
2004         ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
2005         if (pParaPortion)
2006         {
2007             if ( pParaPortion->aWritingDirectionInfos.empty() )
2008                 InitWritingDirections( nPara );
2009 
2010             WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos;
2011             for (const WritingDirectionInfo & rDirInfo : rDirInfos)
2012             {
2013                 if ( ( rDirInfo.nStartPos <= nPos ) && ( rDirInfo.nEndPos >= nPos ) )
2014                 {
2015                     nRightToLeft = rDirInfo.nType;
2016                     if ( pStart )
2017                         *pStart = rDirInfo.nStartPos;
2018                     if ( pEnd )
2019                         *pEnd = rDirInfo.nEndPos;
2020                     break;
2021                 }
2022             }
2023         }
2024     }
2025     return nRightToLeft;
2026 }
2027 
2028 SvxAdjust ImpEditEngine::GetJustification( sal_Int32 nPara ) const
2029 {
2030     SvxAdjust eJustification = SvxAdjust::Left;
2031 
2032     if ( !aStatus.IsOutliner() )
2033     {
2034         eJustification = GetParaAttrib( nPara, EE_PARA_JUST ).GetAdjust();
2035 
2036         if ( IsRightToLeft( nPara ) )
2037         {
2038             if ( eJustification == SvxAdjust::Left )
2039                 eJustification = SvxAdjust::Right;
2040             else if ( eJustification == SvxAdjust::Right )
2041                 eJustification = SvxAdjust::Left;
2042         }
2043     }
2044     return eJustification;
2045 }
2046 
2047 SvxCellJustifyMethod ImpEditEngine::GetJustifyMethod( sal_Int32 nPara ) const
2048 {
2049     const SvxJustifyMethodItem& rItem = GetParaAttrib(nPara, EE_PARA_JUST_METHOD);
2050     return static_cast<SvxCellJustifyMethod>(rItem.GetEnumValue());
2051 }
2052 
2053 SvxCellVerJustify ImpEditEngine::GetVerJustification( sal_Int32 nPara ) const
2054 {
2055     const SvxVerJustifyItem& rItem = GetParaAttrib(nPara, EE_PARA_VER_JUST);
2056     return static_cast<SvxCellVerJustify>(rItem.GetEnumValue());
2057 }
2058 
2059 //  Text changes
2060 void ImpEditEngine::ImpRemoveChars( const EditPaM& rPaM, sal_Int32 nChars )
2061 {
2062     if ( IsUndoEnabled() && !IsInUndo() )
2063     {
2064         const OUString aStr( rPaM.GetNode()->Copy( rPaM.GetIndex(), nChars ) );
2065 
2066         // Check whether attributes are deleted or changed:
2067         const sal_Int32 nStart = rPaM.GetIndex();
2068         const sal_Int32 nEnd = nStart + nChars;
2069         const CharAttribList::AttribsType& rAttribs = rPaM.GetNode()->GetCharAttribs().GetAttribs();
2070         for (const auto & rAttrib : rAttribs)
2071         {
2072             const EditCharAttrib& rAttr = *rAttrib;
2073             if (rAttr.GetEnd() >= nStart && rAttr.GetStart() < nEnd)
2074             {
2075                 EditSelection aSel( rPaM );
2076                 aSel.Max().SetIndex( aSel.Max().GetIndex() + nChars );
2077                 InsertUndo( CreateAttribUndo( aSel, GetEmptyItemSet() ) );
2078                 break;  // for
2079             }
2080         }
2081         InsertUndo(std::make_unique<EditUndoRemoveChars>(pEditEngine, CreateEPaM(rPaM), aStr));
2082     }
2083 
2084     aEditDoc.RemoveChars( rPaM, nChars );
2085 }
2086 
2087 EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 nNewPos )
2088 {
2089     aOldPositions.Justify();
2090     bool bValidAction = ( static_cast<long>(nNewPos) < aOldPositions.Min() ) || ( static_cast<long>(nNewPos) > aOldPositions.Max() );
2091     OSL_ENSURE( bValidAction, "Move in itself?" );
2092     OSL_ENSURE( aOldPositions.Max() <= static_cast<long>(GetParaPortions().Count()), "totally over it: MoveParagraphs" );
2093 
2094     EditSelection aSelection;
2095 
2096     if ( !bValidAction )
2097     {
2098         aSelection = aEditDoc.GetStartPaM();
2099         return aSelection;
2100     }
2101 
2102     sal_Int32 nParaCount = GetParaPortions().Count();
2103 
2104     if ( nNewPos >= nParaCount )
2105         nNewPos = nParaCount;
2106 
2107     // Height may change when moving first or last Paragraph
2108     ParaPortion* pRecalc1 = nullptr;
2109     ParaPortion* pRecalc2 = nullptr;
2110     ParaPortion* pRecalc3 = nullptr;
2111     ParaPortion* pRecalc4 = nullptr;
2112 
2113     if ( nNewPos == 0 ) // Move to Start
2114     {
2115         pRecalc1 = GetParaPortions()[0];
2116         pRecalc2 = GetParaPortions()[aOldPositions.Min()];
2117 
2118     }
2119     else if ( nNewPos == nParaCount )
2120     {
2121         pRecalc1 = GetParaPortions()[nParaCount-1];
2122         pRecalc2 = GetParaPortions()[aOldPositions.Max()];
2123     }
2124 
2125     if ( aOldPositions.Min() == 0 ) // Move from Start
2126     {
2127         pRecalc3 = GetParaPortions()[0];
2128         pRecalc4 = GetParaPortions()[aOldPositions.Max()+1];
2129     }
2130     else if ( aOldPositions.Max() == (nParaCount-1) )
2131     {
2132         pRecalc3 = GetParaPortions()[aOldPositions.Max()];
2133         pRecalc4 = GetParaPortions()[aOldPositions.Min()-1];
2134     }
2135 
2136     MoveParagraphsInfo aMoveParagraphsInfo( aOldPositions.Min(), aOldPositions.Max(), nNewPos );
2137     aBeginMovingParagraphsHdl.Call( aMoveParagraphsInfo );
2138 
2139     if ( IsUndoEnabled() && !IsInUndo())
2140         InsertUndo(std::make_unique<EditUndoMoveParagraphs>(pEditEngine, aOldPositions, nNewPos));
2141 
2142     // do not lose sight of the Position !
2143     ParaPortion* pDestPortion = GetParaPortions().SafeGetObject( nNewPos );
2144 
2145     ParaPortionList aTmpPortionList;
2146     for (long i = aOldPositions.Min(); i <= aOldPositions.Max(); i++  )
2147     {
2148         // always aOldPositions.Min(), since Remove().
2149         std::unique_ptr<ParaPortion> pTmpPortion = GetParaPortions().Release(aOldPositions.Min());
2150         aEditDoc.Release( aOldPositions.Min() );
2151         aTmpPortionList.Append(std::move(pTmpPortion));
2152     }
2153 
2154     sal_Int32 nRealNewPos = pDestPortion ? GetParaPortions().GetPos( pDestPortion ) : GetParaPortions().Count();
2155     OSL_ENSURE( nRealNewPos != EE_PARA_NOT_FOUND, "ImpMoveParagraphs: Invalid Position!" );
2156 
2157     sal_Int32 i = 0;
2158     while( aTmpPortionList.Count() > 0 )
2159     {
2160         std::unique_ptr<ParaPortion> pTmpPortion = aTmpPortionList.Release(0);
2161         if ( i == 0 )
2162             aSelection.Min().SetNode( pTmpPortion->GetNode() );
2163 
2164         aSelection.Max().SetNode( pTmpPortion->GetNode() );
2165         aSelection.Max().SetIndex( pTmpPortion->GetNode()->Len() );
2166 
2167         ContentNode* pN = pTmpPortion->GetNode();
2168         aEditDoc.Insert(nRealNewPos+i, pN);
2169 
2170         GetParaPortions().Insert(nRealNewPos+i, std::move(pTmpPortion));
2171         ++i;
2172     }
2173 
2174     aEndMovingParagraphsHdl.Call( aMoveParagraphsInfo );
2175 
2176     if ( GetNotifyHdl().IsSet() )
2177     {
2178         EENotify aNotify( EE_NOTIFY_PARAGRAPHSMOVED );
2179         aNotify.nParagraph = nNewPos;
2180         aNotify.nParam1 = aOldPositions.Min();
2181         aNotify.nParam2 = aOldPositions.Max();
2182         GetNotifyHdl().Call( aNotify );
2183     }
2184 
2185     aEditDoc.SetModified( true );
2186 
2187     if ( pRecalc1 )
2188         CalcHeight( pRecalc1 );
2189     if ( pRecalc2 )
2190         CalcHeight( pRecalc2 );
2191     if ( pRecalc3 )
2192         CalcHeight( pRecalc3 );
2193     if ( pRecalc4 )
2194         CalcHeight( pRecalc4 );
2195 
2196 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
2197     ParaPortionList::DbgCheck(GetParaPortions(), aEditDoc);
2198 #endif
2199     return aSelection;
2200 }
2201 
2202 
2203 EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward )
2204 {
2205     OSL_ENSURE( pLeft != pRight, "Join together the same paragraph ?" );
2206     OSL_ENSURE( aEditDoc.GetPos( pLeft ) != EE_PARA_NOT_FOUND, "Inserted node not found (1)" );
2207     OSL_ENSURE( aEditDoc.GetPos( pRight ) != EE_PARA_NOT_FOUND, "Inserted node not found (2)" );
2208 
2209     // #i120020# it is possible that left and right are *not* in the desired order (left/right)
2210     // so correct it. This correction is needed, else an invalid SfxLinkUndoAction will be
2211     // created from ConnectParagraphs below. Assert this situation, it should be corrected by the
2212     // caller.
2213     if(aEditDoc.GetPos( pLeft ) > aEditDoc.GetPos( pRight ))
2214     {
2215         OSL_ENSURE(false, "ImpConnectParagraphs with wrong order of pLeft/pRight nodes (!)");
2216         std::swap(pLeft, pRight);
2217     }
2218 
2219     sal_Int32 nParagraphTobeDeleted = aEditDoc.GetPos( pRight );
2220     aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pRight, nParagraphTobeDeleted ));
2221 
2222     GetEditEnginePtr()->ParagraphConnected( aEditDoc.GetPos( pLeft ), aEditDoc.GetPos( pRight ) );
2223 
2224     if ( IsUndoEnabled() && !IsInUndo() )
2225     {
2226         InsertUndo( std::make_unique<EditUndoConnectParas>(pEditEngine,
2227             aEditDoc.GetPos( pLeft ), pLeft->Len(),
2228             pLeft->GetContentAttribs().GetItems(), pRight->GetContentAttribs().GetItems(),
2229             pLeft->GetStyleSheet(), pRight->GetStyleSheet(), bBackward ) );
2230     }
2231 
2232     if ( bBackward )
2233     {
2234         pLeft->SetStyleSheet( pRight->GetStyleSheet() );
2235         pLeft->GetContentAttribs().GetItems().Set( pRight->GetContentAttribs().GetItems() );
2236         pLeft->GetCharAttribs().GetDefFont() = pRight->GetCharAttribs().GetDefFont();
2237     }
2238 
2239     ParaAttribsChanged( pLeft, true );
2240 
2241     // First search for Portions since pRight is gone after ConnectParagraphs.
2242     ParaPortion* pLeftPortion = FindParaPortion( pLeft );
2243     OSL_ENSURE( pLeftPortion, "Blind Portion in ImpConnectParagraphs(1)" );
2244 
2245     if ( GetStatus().DoOnlineSpelling() )
2246     {
2247         sal_Int32 nEnd = pLeft->Len();
2248         sal_Int32 nInv = nEnd ? nEnd-1 : nEnd;
2249         pLeft->GetWrongList()->ClearWrongs( nInv, static_cast<size_t>(-1), pLeft );  // Possibly remove one
2250         pLeft->GetWrongList()->SetInvalidRange(nInv, nEnd+1);
2251         // Take over misspelled words
2252         WrongList* pRWrongs = pRight->GetWrongList();
2253         for (auto & elem : *pRWrongs)
2254         {
2255             if (elem.mnStart != 0)   // Not a subsequent
2256             {
2257                 elem.mnStart = elem.mnStart + nEnd;
2258                 elem.mnEnd = elem.mnEnd + nEnd;
2259                 pLeft->GetWrongList()->push_back(elem);
2260             }
2261         }
2262     }
2263 
2264     if ( IsCallParaInsertedOrDeleted() )
2265         GetEditEnginePtr()->ParagraphDeleted( nParagraphTobeDeleted );
2266 
2267     EditPaM aPaM = aEditDoc.ConnectParagraphs( pLeft, pRight );
2268     GetParaPortions().Remove( nParagraphTobeDeleted );
2269 
2270     pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() );
2271 
2272     // the right node is deleted by EditDoc:ConnectParagraphs().
2273     if ( GetTextRanger() )
2274     {
2275         // By joining together the two, the left is although reformatted,
2276         // however if its height does not change then the formatting receives
2277         // the change of the total text height too late...
2278         for ( sal_Int32 n = nParagraphTobeDeleted; n < GetParaPortions().Count(); n++ )
2279         {
2280             ParaPortion* pPP = GetParaPortions()[n];
2281             pPP->MarkSelectionInvalid( 0 );
2282             pPP->GetLines().Reset();
2283         }
2284     }
2285 
2286     TextModified();
2287 
2288     return aPaM;
2289 }
2290 
2291 EditPaM ImpEditEngine::DeleteLeftOrRight( const EditSelection& rSel, sal_uInt8 nMode, DeleteMode nDelMode )
2292 {
2293     OSL_ENSURE( !rSel.DbgIsBuggy( aEditDoc ), "Index out of range in DeleteLeftOrRight" );
2294 
2295     if ( rSel.HasRange() )  // only then Delete Selection
2296         return ImpDeleteSelection( rSel );
2297 
2298     EditPaM aCurPos( rSel.Max() );
2299     EditPaM aDelStart( aCurPos );
2300     EditPaM aDelEnd( aCurPos );
2301     if ( nMode == DEL_LEFT )
2302     {
2303         if ( nDelMode == DeleteMode::Simple )
2304         {
2305             sal_uInt16 nCharMode = i18n::CharacterIteratorMode::SKIPCHARACTER;
2306             // Check if we are deleting a CJK ideograph variance sequence (IVS).
2307             sal_Int32 nIndex = aCurPos.GetIndex();
2308             if (nIndex > 0)
2309             {
2310                 const OUString& rString = aCurPos.GetNode()->GetString();
2311                 sal_Int32 nCode = rString.iterateCodePoints(&nIndex, -1);
2312                 if (unicode::isIVSSelector(nCode) && nIndex > 0 &&
2313                         unicode::isCJKIVSCharacter(rString.iterateCodePoints(&nIndex, -1)))
2314                 {
2315                     nCharMode = i18n::CharacterIteratorMode::SKIPCELL;
2316                 }
2317             }
2318             aDelStart = CursorLeft(aCurPos, nCharMode);
2319         }
2320         else if ( nDelMode == DeleteMode::RestOfWord )
2321         {
2322             aDelStart = StartOfWord( aCurPos );
2323             if ( aDelStart.GetIndex() == aCurPos.GetIndex() )
2324                 aDelStart = WordLeft( aCurPos );
2325         }
2326         else    // DELMODE_RESTOFCONTENT
2327         {
2328             aDelStart.SetIndex( 0 );
2329             if ( aDelStart == aCurPos )
2330             {
2331                 // Complete paragraph previous
2332                 ContentNode* pPrev = GetPrevVisNode( aCurPos.GetNode() );
2333                 if ( pPrev )
2334                     aDelStart = EditPaM( pPrev, 0 );
2335             }
2336         }
2337     }
2338     else
2339     {
2340         if ( nDelMode == DeleteMode::Simple )
2341         {
2342             aDelEnd = CursorRight( aCurPos );
2343         }
2344         else if ( nDelMode == DeleteMode::RestOfWord )
2345         {
2346             aDelEnd = EndOfWord( aCurPos );
2347 
2348             if (aDelEnd.GetIndex() == aCurPos.GetIndex())
2349             {
2350                 const sal_Int32 nLen(aCurPos.GetNode()->Len());
2351 
2352                 // #i120020# when 0 == nLen, aDelStart needs to be adapted, not
2353                 // aDelEnd. This would (and did) lead to a wrong order in the
2354                 // ImpConnectParagraphs call later.
2355                 if(nLen)
2356                 {
2357                     // end of para?
2358                     if (aDelEnd.GetIndex() == nLen)
2359                     {
2360                         aDelEnd = WordLeft( aCurPos );
2361                     }
2362                     else // there's still sth to delete on the right
2363                     {
2364                         aDelEnd = EndOfWord( WordRight( aCurPos ) );
2365                         // if there'n no next word...
2366                         if (aDelEnd.GetIndex() == nLen )
2367                         {
2368                             aDelEnd.SetIndex( nLen );
2369                         }
2370                     }
2371                 }
2372                 else
2373                 {
2374                     aDelStart = WordLeft(aCurPos);
2375                 }
2376             }
2377         }
2378         else    // DELMODE_RESTOFCONTENT
2379         {
2380             aDelEnd.SetIndex( aCurPos.GetNode()->Len() );
2381             if ( aDelEnd == aCurPos )
2382             {
2383                 // Complete paragraph next
2384                 ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() );
2385                 if ( pNext )
2386                     aDelEnd = EditPaM( pNext, pNext->Len() );
2387             }
2388         }
2389     }
2390 
2391     // ConnectParagraphs not enough for different Nodes when
2392     // DeleteMode::RestOfContent.
2393     if ( ( nDelMode == DeleteMode::RestOfContent ) || ( aDelStart.GetNode() == aDelEnd.GetNode() ) )
2394         return ImpDeleteSelection( EditSelection( aDelStart, aDelEnd ) );
2395 
2396     return ImpConnectParagraphs(aDelStart.GetNode(), aDelEnd.GetNode());
2397 }
2398 
2399 EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel)
2400 {
2401     if ( !rCurSel.HasRange() )
2402         return rCurSel.Min();
2403 
2404     EditSelection aCurSel(rCurSel);
2405     aCurSel.Adjust( aEditDoc );
2406     EditPaM aStartPaM(aCurSel.Min());
2407     EditPaM aEndPaM(aCurSel.Max());
2408 
2409     CursorMoved( aStartPaM.GetNode() ); // only so that newly set Attributes disappear...
2410     CursorMoved( aEndPaM.GetNode() );   // only so that newly set Attributes disappear...
2411 
2412     OSL_ENSURE( aStartPaM.GetIndex() <= aStartPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" );
2413     OSL_ENSURE( aEndPaM.GetIndex() <= aEndPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" );
2414 
2415     sal_Int32 nStartNode = aEditDoc.GetPos( aStartPaM.GetNode() );
2416     sal_Int32 nEndNode = aEditDoc.GetPos( aEndPaM.GetNode() );
2417 
2418     OSL_ENSURE( nEndNode != EE_PARA_NOT_FOUND, "Start > End ?!" );
2419     OSL_ENSURE( nStartNode <= nEndNode, "Start > End ?!" );
2420 
2421     // Remove all nodes in between ....
2422     for ( sal_Int32 z = nStartNode+1; z < nEndNode; z++ )
2423     {
2424         // Always nStartNode+1, due to Remove()!
2425         ImpRemoveParagraph( nStartNode+1 );
2426     }
2427 
2428     if ( aStartPaM.GetNode() != aEndPaM.GetNode() )
2429     {
2430         // The Rest of the StartNodes...
2431         ImpRemoveChars( aStartPaM, aStartPaM.GetNode()->Len() - aStartPaM.GetIndex() );
2432         ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() );
2433         OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(3)" );
2434         pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() );
2435 
2436         // The beginning of the EndNodes....
2437         const sal_Int32 nChars = aEndPaM.GetIndex();
2438         aEndPaM.SetIndex( 0 );
2439         ImpRemoveChars( aEndPaM, nChars );
2440         pPortion = FindParaPortion( aEndPaM.GetNode() );
2441         OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(4)" );
2442         pPortion->MarkSelectionInvalid( 0 );
2443         // Join together....
2444         aStartPaM = ImpConnectParagraphs( aStartPaM.GetNode(), aEndPaM.GetNode() );
2445     }
2446     else
2447     {
2448         ImpRemoveChars( aStartPaM, aEndPaM.GetIndex() - aStartPaM.GetIndex() );
2449         ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() );
2450         OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(5)" );
2451         pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() );
2452     }
2453 
2454     UpdateSelections();
2455     TextModified();
2456     return aStartPaM;
2457 }
2458 
2459 void ImpEditEngine::ImpRemoveParagraph( sal_Int32 nPara )
2460 {
2461     ContentNode* pNode = aEditDoc.GetObject( nPara );
2462     ContentNode* pNextNode = aEditDoc.GetObject( nPara+1 );
2463 
2464     OSL_ENSURE( pNode, "Blind Node in ImpRemoveParagraph" );
2465 
2466     aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pNode, nPara ));
2467 
2468     // The node is managed by the undo and possibly destroyed!
2469     aEditDoc.Release( nPara );
2470     GetParaPortions().Remove( nPara );
2471 
2472     if ( IsCallParaInsertedOrDeleted() )
2473     {
2474         GetEditEnginePtr()->ParagraphDeleted( nPara );
2475     }
2476 
2477     // Extra-Space may be determined again in the following. For
2478     // ParaAttribsChanged the paragraph is unfortunately formatted again,
2479     // however this method should not be time critical!
2480     if ( pNextNode )
2481         ParaAttribsChanged( pNextNode );
2482 
2483     if ( IsUndoEnabled() && !IsInUndo() )
2484         InsertUndo(std::make_unique<EditUndoDelContent>(pEditEngine, pNode, nPara));
2485     else
2486     {
2487         aEditDoc.RemoveItemsFromPool(*pNode);
2488         if ( pNode->GetStyleSheet() )
2489             EndListening( *pNode->GetStyleSheet() );
2490         delete pNode;
2491     }
2492 }
2493 
2494 EditPaM ImpEditEngine::AutoCorrect( const EditSelection& rCurSel, sal_Unicode c,
2495                                     bool bOverwrite, vcl::Window const * pFrameWin )
2496 {
2497     // i.e. Calc has special needs regarding a leading single quotation mark
2498     // when starting cell input.
2499     if (c == '\'' && !IsReplaceLeadingSingleQuotationMark() &&
2500             rCurSel.Min() == rCurSel.Max() && rCurSel.Max().GetIndex() == 0)
2501     {
2502         return InsertTextUserInput( rCurSel, c, bOverwrite );
2503     }
2504 
2505     EditSelection aSel( rCurSel );
2506     SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
2507     if ( pAutoCorrect )
2508     {
2509         if ( aSel.HasRange() )
2510             aSel = ImpDeleteSelection( rCurSel );
2511 
2512         // #i78661 allow application to turn off capitalization of
2513         // start sentence explicitly.
2514         // (This is done by setting IsFirstWordCapitalization to sal_False.)
2515         bool bOldCapitalStartSentence = pAutoCorrect->IsAutoCorrFlag( ACFlags::CapitalStartSentence );
2516         if (!IsFirstWordCapitalization())
2517         {
2518             ESelection aESel( CreateESel(aSel) );
2519             EditSelection aFirstWordSel;
2520             EditSelection aSecondWordSel;
2521             if (aESel.nEndPara == 0)    // is this the first para?
2522             {
2523                 // select first word...
2524                 // start by checking if para starts with word.
2525                 aFirstWordSel = SelectWord( CreateSel(ESelection()) );
2526                 if (aFirstWordSel.Min().GetIndex() == 0 && aFirstWordSel.Max().GetIndex() == 0)
2527                 {
2528                     // para does not start with word -> select next/first word
2529                     EditPaM aRightWord( WordRight( aFirstWordSel.Max() ) );
2530                     aFirstWordSel = SelectWord( EditSelection( aRightWord ) );
2531                 }
2532 
2533                 // select second word
2534                 // (sometimes aSel might not point to the end of the first word
2535                 // but to some following char like '.'. ':', ...
2536                 // In those cases we need aSecondWordSel to see if aSel
2537                 // will actually effect the first word.)
2538                 EditPaM aRight2Word( WordRight( aFirstWordSel.Max() ) );
2539                 aSecondWordSel = SelectWord( EditSelection( aRight2Word ) );
2540             }
2541             bool bIsFirstWordInFirstPara = aESel.nEndPara == 0 &&
2542                     aFirstWordSel.Max().GetIndex() <= aSel.Max().GetIndex() &&
2543                     aSel.Max().GetIndex() <= aSecondWordSel.Min().GetIndex();
2544 
2545             if (bIsFirstWordInFirstPara)
2546                 pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, IsFirstWordCapitalization() );
2547         }
2548 
2549         ContentNode* pNode = aSel.Max().GetNode();
2550         const sal_Int32 nIndex = aSel.Max().GetIndex();
2551         EdtAutoCorrDoc aAuto(pEditEngine, pNode, nIndex, c);
2552         // FIXME: this _must_ be called with reference to the actual node text!
2553         OUString const& rNodeString(pNode->GetString());
2554         pAutoCorrect->DoAutoCorrect(
2555             aAuto, rNodeString, nIndex, c, !bOverwrite, mbNbspRunNext, pFrameWin );
2556         aSel.Max().SetIndex( aAuto.GetCursor() );
2557 
2558         // #i78661 since the SvxAutoCorrect object used here is
2559         // shared we need to reset the value to its original state.
2560         pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, bOldCapitalStartSentence );
2561     }
2562     return aSel.Max();
2563 }
2564 
2565 
2566 EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel,
2567         sal_Unicode c, bool bOverwrite )
2568 {
2569     OSL_ENSURE( c != '\t', "Tab for InsertText ?" );
2570     OSL_ENSURE( c != '\n', "Word wrapping for InsertText ?");
2571 
2572     EditPaM aPaM( rCurSel.Min() );
2573 
2574     bool bDoOverwrite = bOverwrite &&
2575             ( aPaM.GetIndex() < aPaM.GetNode()->Len() );
2576 
2577     bool bUndoAction = ( rCurSel.HasRange() || bDoOverwrite );
2578 
2579     if ( bUndoAction )
2580         UndoActionStart( EDITUNDO_INSERT );
2581 
2582     if ( rCurSel.HasRange() )
2583     {
2584         aPaM = ImpDeleteSelection( rCurSel );
2585     }
2586     else if ( bDoOverwrite )
2587     {
2588         // If selected, then do not also overwrite a character!
2589         EditSelection aTmpSel( aPaM );
2590         aTmpSel.Max().SetIndex( aTmpSel.Max().GetIndex()+1 );
2591         OSL_ENSURE( !aTmpSel.DbgIsBuggy( aEditDoc ), "Overwrite: Wrong selection! ");
2592         ImpDeleteSelection( aTmpSel );
2593     }
2594 
2595     if ( aPaM.GetNode()->Len() < MAXCHARSINPARA )
2596     {
2597         if (IsInputSequenceCheckingRequired( c, rCurSel ))
2598         {
2599             uno::Reference < i18n::XExtendedInputSequenceChecker > _xISC( ImplGetInputSequenceChecker() );
2600             if (!pCTLOptions)
2601                 pCTLOptions.reset( new SvtCTLOptions );
2602 
2603             if (_xISC)
2604             {
2605                 const sal_Int32 nTmpPos = aPaM.GetIndex();
2606                 sal_Int16 nCheckMode = pCTLOptions->IsCTLSequenceCheckingRestricted() ?
2607                         i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
2608 
2609                 // the text that needs to be checked is only the one
2610                 // before the current cursor position
2611                 const OUString aOldText( aPaM.GetNode()->Copy(0, nTmpPos) );
2612                 OUString aNewText( aOldText );
2613                 if (pCTLOptions->IsCTLSequenceCheckingTypeAndReplace())
2614                 {
2615                     _xISC->correctInputSequence(aNewText, nTmpPos - 1, c, nCheckMode);
2616 
2617                     // find position of first character that has changed
2618                     sal_Int32 nOldLen = aOldText.getLength();
2619                     sal_Int32 nNewLen = aNewText.getLength();
2620                     const sal_Unicode *pOldTxt = aOldText.getStr();
2621                     const sal_Unicode *pNewTxt = aNewText.getStr();
2622                     sal_Int32 nChgPos = 0;
2623                     while ( nChgPos < nOldLen && nChgPos < nNewLen &&
2624                             pOldTxt[nChgPos] == pNewTxt[nChgPos] )
2625                         ++nChgPos;
2626 
2627                     const OUString aChgText( aNewText.copy( nChgPos ) );
2628 
2629                     // select text from first pos to be changed to current pos
2630                     EditSelection aSel( EditPaM( aPaM.GetNode(), nChgPos ), aPaM );
2631 
2632                     if (!aChgText.isEmpty())
2633                         return InsertText( aSel, aChgText ); // implicitly handles undo
2634                     else
2635                         return aPaM;
2636                 }
2637                 else
2638                 {
2639                     // should the character be ignored (i.e. not get inserted) ?
2640                     if (!_xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode ))
2641                         return aPaM;    // nothing to be done -> no need for undo
2642                 }
2643             }
2644 
2645             // at this point now we will insert the character 'normally' some lines below...
2646         }
2647 
2648         if ( IsUndoEnabled() && !IsInUndo() )
2649         {
2650             std::unique_ptr<EditUndoInsertChars> pNewUndo(new EditUndoInsertChars(pEditEngine, CreateEPaM(aPaM), OUString(c)));
2651             bool bTryMerge = !bDoOverwrite && ( c != ' ' );
2652             InsertUndo( std::move(pNewUndo), bTryMerge );
2653         }
2654 
2655         aEditDoc.InsertText( aPaM, OUString(c) );
2656         ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() );
2657         OSL_ENSURE( pPortion, "Blind Portion in InsertText" );
2658         pPortion->MarkInvalid( aPaM.GetIndex(), 1 );
2659         aPaM.SetIndex( aPaM.GetIndex()+1 );   // does not do EditDoc-Method anymore
2660     }
2661 
2662     TextModified();
2663 
2664     if ( bUndoAction )
2665         UndoActionEnd();
2666 
2667     return aPaM;
2668 }
2669 
2670 EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUString& rStr)
2671 {
2672     UndoActionStart( EDITUNDO_INSERT );
2673 
2674     EditPaM aPaM;
2675     if ( aCurSel.HasRange() )
2676         aPaM = ImpDeleteSelection( aCurSel );
2677     else
2678         aPaM = aCurSel.Max();
2679 
2680     EditPaM aCurPaM( aPaM );    // for the Invalidate
2681 
2682     // get word boundaries in order to clear possible WrongList entries
2683     // and invalidate all the necessary text (everything after and including the
2684     // start of the word)
2685     // #i107201# do the expensive SelectWord call only if online spelling is active
2686     EditSelection aCurWord;
2687     if ( GetStatus().DoOnlineSpelling() )
2688         aCurWord = SelectWord( aCurPaM, i18n::WordType::DICTIONARY_WORD );
2689 
2690     OUString aText(convertLineEnd(rStr, LINEEND_LF));
2691     if (utl::ConfigManager::IsFuzzing())    //tab expansion performance in editeng is appalling
2692         aText = aText.replaceAll("\t","-");
2693     SfxVoidItem aTabItem( EE_FEATURE_TAB );
2694 
2695     // Converts to linesep = \n
2696     // Token LINE_SEP query,
2697     // since the MAC-Compiler makes something else from \n !
2698 
2699     sal_Int32 nStart = 0;
2700     while ( nStart < aText.getLength() )
2701     {
2702         sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart );
2703         if ( nEnd == -1 )
2704             nEnd = aText.getLength(); // not dereference!
2705 
2706         // Start == End => empty line
2707         if ( nEnd > nStart )
2708         {
2709             OUString aLine = aText.copy( nStart, nEnd-nStart );
2710             sal_Int32 nExistingChars = aPaM.GetNode()->Len();
2711             sal_Int32 nChars = nExistingChars + aLine.getLength();
2712             if (nChars > MAXCHARSINPARA)
2713             {
2714                 sal_Int32 nMaxNewChars = std::max<sal_Int32>(0, MAXCHARSINPARA - nExistingChars);
2715                 nEnd -= ( aLine.getLength() - nMaxNewChars ); // Then the characters end up in the next paragraph.
2716                 aLine = aLine.copy( 0, nMaxNewChars );        // Delete the Rest...
2717             }
2718             if ( IsUndoEnabled() && !IsInUndo() )
2719                 InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), aLine));
2720             // Tabs ?
2721             if ( aLine.indexOf( '\t' ) == -1 )
2722                 aPaM = aEditDoc.InsertText( aPaM, aLine );
2723             else
2724             {
2725                 sal_Int32 nStart2 = 0;
2726                 while ( nStart2 < aLine.getLength() )
2727                 {
2728                     sal_Int32 nEnd2 = aLine.indexOf( "\t", nStart2 );
2729                     if ( nEnd2 == -1 )
2730                         nEnd2 = aLine.getLength();    // not dereference!
2731 
2732                     if ( nEnd2 > nStart2 )
2733                         aPaM = aEditDoc.InsertText( aPaM, aLine.copy( nStart2, nEnd2-nStart2 ) );
2734                     if ( nEnd2 < aLine.getLength() )
2735                     {
2736                         aPaM = aEditDoc.InsertFeature( aPaM, aTabItem );
2737                     }
2738                     nStart2 = nEnd2+1;
2739                 }
2740             }
2741             ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() );
2742             OSL_ENSURE( pPortion, "Blind Portion in InsertText" );
2743 
2744             if ( GetStatus().DoOnlineSpelling() )
2745             {
2746                 // now remove the Wrongs (red spell check marks) from both words...
2747                 WrongList *pWrongs = aCurPaM.GetNode()->GetWrongList();
2748                 if (pWrongs && !pWrongs->empty())
2749                     pWrongs->ClearWrongs( aCurWord.Min().GetIndex(), aPaM.GetIndex(), aPaM.GetNode() );
2750                 // ... and mark both words as 'to be checked again'
2751                 pPortion->MarkInvalid( aCurWord.Min().GetIndex(), aLine.getLength() );
2752             }
2753             else
2754                 pPortion->MarkInvalid( aCurPaM.GetIndex(), aLine.getLength() );
2755         }
2756         if ( nEnd < aText.getLength() )
2757             aPaM = ImpInsertParaBreak( aPaM );
2758 
2759         nStart = nEnd+1;
2760     }
2761 
2762     UndoActionEnd();
2763 
2764     TextModified();
2765     return aPaM;
2766 }
2767 
2768 EditPaM ImpEditEngine::ImpFastInsertText( EditPaM aPaM, const OUString& rStr )
2769 {
2770     OSL_ENSURE( rStr.indexOf( 0x0A ) == -1, "FastInsertText: Newline not allowed! ");
2771     OSL_ENSURE( rStr.indexOf( 0x0D ) == -1, "FastInsertText: Newline not allowed! ");
2772     OSL_ENSURE( rStr.indexOf( '\t' ) == -1, "FastInsertText: Newline not allowed! ");
2773 
2774     if ( ( aPaM.GetNode()->Len() + rStr.getLength() ) < MAXCHARSINPARA )
2775     {
2776         if ( IsUndoEnabled() && !IsInUndo() )
2777             InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), rStr));
2778 
2779         aPaM = aEditDoc.InsertText( aPaM, rStr );
2780         TextModified();
2781     }
2782     else
2783     {
2784         aPaM = ImpInsertText( aPaM, rStr );
2785     }
2786 
2787     return aPaM;
2788 }
2789 
2790 EditPaM ImpEditEngine::ImpInsertFeature(const EditSelection& rCurSel, const SfxPoolItem& rItem)
2791 {
2792     EditPaM aPaM;
2793     if ( rCurSel.HasRange() )
2794         aPaM = ImpDeleteSelection( rCurSel );
2795     else
2796         aPaM = rCurSel.Max();
2797 
2798     if ( aPaM.GetIndex() >= SAL_MAX_INT32-1 )
2799         return aPaM;
2800 
2801     if ( IsUndoEnabled() && !IsInUndo() )
2802         InsertUndo(std::make_unique<EditUndoInsertFeature>(pEditEngine, CreateEPaM(aPaM), rItem));
2803     aPaM = aEditDoc.InsertFeature( aPaM, rItem );
2804     UpdateFields();
2805 
2806     ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() );
2807     OSL_ENSURE( pPortion, "Blind Portion in InsertFeature" );
2808     pPortion->MarkInvalid( aPaM.GetIndex()-1, 1 );
2809 
2810     TextModified();
2811 
2812     return aPaM;
2813 }
2814 
2815 EditPaM ImpEditEngine::ImpInsertParaBreak( const EditSelection& rCurSel )
2816 {
2817     EditPaM aPaM;
2818     if ( rCurSel.HasRange() )
2819         aPaM = ImpDeleteSelection( rCurSel );
2820     else
2821         aPaM = rCurSel.Max();
2822 
2823     return ImpInsertParaBreak( aPaM );
2824 }
2825 
2826 EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs )
2827 {
2828     if ( aEditDoc.Count() >= EE_PARA_MAX_COUNT )
2829     {
2830         SAL_WARN( "editeng", "ImpEditEngine::ImpInsertParaBreak - can't process more than "
2831                 << EE_PARA_MAX_COUNT << " paragraphs!");
2832         return rPaM;
2833     }
2834 
2835     if ( IsUndoEnabled() && !IsInUndo() )
2836         InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, aEditDoc.GetPos(rPaM.GetNode()), rPaM.GetIndex()));
2837 
2838     EditPaM aPaM( aEditDoc.InsertParaBreak( rPaM, bKeepEndingAttribs ) );
2839 
2840     if ( GetStatus().DoOnlineSpelling() )
2841     {
2842         sal_Int32 nEnd = rPaM.GetNode()->Len();
2843         aPaM.GetNode()->CreateWrongList();
2844         WrongList* pLWrongs = rPaM.GetNode()->GetWrongList();
2845         WrongList* pRWrongs = aPaM.GetNode()->GetWrongList();
2846         // take over misspelled words:
2847         for (auto & elem : *pLWrongs)
2848         {
2849             // Correct only if really a word gets overlapped in the process of
2850             // Spell checking
2851             if (elem.mnStart > static_cast<size_t>(nEnd))
2852             {
2853                 pRWrongs->push_back(elem);
2854                 editeng::MisspellRange& rRWrong = pRWrongs->back();
2855                 rRWrong.mnStart = rRWrong.mnStart - nEnd;
2856                 rRWrong.mnEnd = rRWrong.mnEnd - nEnd;
2857             }
2858             else if (elem.mnStart < static_cast<size_t>(nEnd) && elem.mnEnd > static_cast<size_t>(nEnd))
2859                 elem.mnEnd = nEnd;
2860         }
2861         sal_Int32 nInv = nEnd ? nEnd-1 : nEnd;
2862         if ( nEnd )
2863             pLWrongs->SetInvalidRange(nInv, nEnd);
2864         else
2865             pLWrongs->SetValid();
2866         pRWrongs->SetValid();
2867         pRWrongs->SetInvalidRange(0, 1);  // Only test the first word
2868     }
2869 
2870     ParaPortion* pPortion = FindParaPortion( rPaM.GetNode() );
2871     OSL_ENSURE( pPortion, "Blind Portion in ImpInsertParaBreak" );
2872     pPortion->MarkInvalid( rPaM.GetIndex(), 0 );
2873 
2874     // Optimization: Do not place unnecessarily many getPos to Listen!
2875     // Here, as in undo, but also in all other methods.
2876     sal_Int32 nPos = GetParaPortions().GetPos( pPortion );
2877     ParaPortion* pNewPortion = new ParaPortion( aPaM.GetNode() );
2878     GetParaPortions().Insert(nPos+1, std::unique_ptr<ParaPortion>(pNewPortion));
2879     ParaAttribsChanged( pNewPortion->GetNode() );
2880     if ( IsCallParaInsertedOrDeleted() )
2881         GetEditEnginePtr()->ParagraphInserted( nPos+1 );
2882 
2883     CursorMoved( rPaM.GetNode() );  // if empty Attributes have emerged.
2884     TextModified();
2885     return aPaM;
2886 }
2887 
2888 EditPaM ImpEditEngine::ImpFastInsertParagraph( sal_Int32 nPara )
2889 {
2890     if ( IsUndoEnabled() && !IsInUndo() )
2891     {
2892         if ( nPara )
2893         {
2894             OSL_ENSURE( aEditDoc.GetObject( nPara-1 ), "FastInsertParagraph: Prev does not exist" );
2895             InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, nPara-1, aEditDoc.GetObject( nPara-1 )->Len()));
2896         }
2897         else
2898             InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, 0, 0));
2899     }
2900 
2901     ContentNode* pNode = new ContentNode( aEditDoc.GetItemPool() );
2902     // If flat mode, then later no Font is set:
2903     pNode->GetCharAttribs().GetDefFont() = aEditDoc.GetDefFont();
2904 
2905     if ( GetStatus().DoOnlineSpelling() )
2906         pNode->CreateWrongList();
2907 
2908     aEditDoc.Insert(nPara, pNode);
2909 
2910     GetParaPortions().Insert(nPara, std::make_unique<ParaPortion>( pNode ));
2911     if ( IsCallParaInsertedOrDeleted() )
2912         GetEditEnginePtr()->ParagraphInserted( nPara );
2913 
2914     return EditPaM( pNode, 0 );
2915 }
2916 
2917 EditPaM ImpEditEngine::InsertParaBreak(const EditSelection& rCurSel)
2918 {
2919     EditPaM aPaM(ImpInsertParaBreak(rCurSel));
2920     if ( aStatus.DoAutoIndenting() )
2921     {
2922         sal_Int32 nPara = aEditDoc.GetPos( aPaM.GetNode() );
2923         OSL_ENSURE( nPara > 0, "AutoIndenting: Error!" );
2924         const OUString aPrevParaText( GetEditDoc().GetParaAsString( nPara-1 ) );
2925         sal_Int32 n = 0;
2926         while ( ( n < aPrevParaText.getLength() ) &&
2927                 ( ( aPrevParaText[n] == ' ' ) || ( aPrevParaText[n] == '\t' ) ) )
2928         {
2929             if ( aPrevParaText[n] == '\t' )
2930                 aPaM = ImpInsertFeature( aPaM, SfxVoidItem( EE_FEATURE_TAB ) );
2931             else
2932                 aPaM = ImpInsertText( aPaM, OUString(aPrevParaText[n]) );
2933             n++;
2934         }
2935 
2936     }
2937     return aPaM;
2938 }
2939 
2940 EditPaM ImpEditEngine::InsertTab(const EditSelection& rCurSel)
2941 {
2942     EditPaM aPaM( ImpInsertFeature(rCurSel, SfxVoidItem(EE_FEATURE_TAB )));
2943     return aPaM;
2944 }
2945 
2946 EditPaM ImpEditEngine::InsertField(const EditSelection& rCurSel, const SvxFieldItem& rFld)
2947 {
2948     return ImpInsertFeature(rCurSel, rFld);
2949 }
2950 
2951 bool ImpEditEngine::UpdateFields()
2952 {
2953     bool bChanges = false;
2954     sal_Int32 nParas = GetEditDoc().Count();
2955     for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ )
2956     {
2957         bool bChangesInPara = false;
2958         ContentNode* pNode = GetEditDoc().GetObject( nPara );
2959         OSL_ENSURE( pNode, "NULL-Pointer in Doc" );
2960         CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs();
2961         for (std::unique_ptr<EditCharAttrib> & rAttrib : rAttribs)
2962         {
2963             EditCharAttrib& rAttr = *rAttrib;
2964             if (rAttr.Which() == EE_FEATURE_FIELD)
2965             {
2966                 EditCharAttribField& rField = static_cast<EditCharAttribField&>(rAttr);
2967                 std::unique_ptr<EditCharAttribField> pCurrent(new EditCharAttribField(rField));
2968                 rField.Reset();
2969 
2970                 if (!aStatus.MarkNonUrlFields() && !aStatus.MarkUrlFields())
2971                     ;   // nothing marked
2972                 else if (aStatus.MarkNonUrlFields() && aStatus.MarkUrlFields())
2973                     rField.GetFieldColor() = GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor;
2974                 else
2975                 {
2976                     bool bURL = false;
2977                     if (const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(rField.GetItem()))
2978                     {
2979                         if (const SvxFieldData* pFieldData = pFieldItem->GetField())
2980                             bURL = (dynamic_cast<const SvxURLField* >(pFieldData) != nullptr);
2981                     }
2982                     if ((bURL && aStatus.MarkUrlFields()) || (!bURL && aStatus.MarkNonUrlFields()))
2983                         rField.GetFieldColor() = GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor;
2984                 }
2985 
2986                 const OUString aFldValue =
2987                     GetEditEnginePtr()->CalcFieldValue(
2988                         static_cast<const SvxFieldItem&>(*rField.GetItem()),
2989                         nPara, rField.GetStart(), rField.GetTextColor(), rField.GetFieldColor());
2990 
2991                 rField.SetFieldValue(aFldValue);
2992                 if (rField != *pCurrent)
2993                 {
2994                     bChanges = true;
2995                     bChangesInPara = true;
2996                 }
2997             }
2998         }
2999         if ( bChangesInPara )
3000         {
3001             // If possible be more precise when invalidate.
3002             ParaPortion* pPortion = GetParaPortions()[nPara];
3003             OSL_ENSURE( pPortion, "NULL-Pointer in Doc" );
3004             pPortion->MarkSelectionInvalid( 0 );
3005         }
3006     }
3007     return bChanges;
3008 }
3009 
3010 EditPaM ImpEditEngine::InsertLineBreak(const EditSelection& aCurSel)
3011 {
3012     EditPaM aPaM( ImpInsertFeature( aCurSel, SfxVoidItem( EE_FEATURE_LINEBR ) ) );
3013     return aPaM;
3014 }
3015 
3016 
3017 //  Helper functions
3018 
3019 tools::Rectangle ImpEditEngine::PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags )
3020 {
3021     OSL_ENSURE( GetUpdateMode(), "Must not be reached when Update=FALSE: PaMtoEditCursor" );
3022 
3023     tools::Rectangle aEditCursor;
3024     long nY = 0;
3025     for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ )
3026     {
3027         ParaPortion* pPortion = GetParaPortions()[nPortion];
3028         ContentNode* pNode = pPortion->GetNode();
3029         OSL_ENSURE( pNode, "Invalid Node in Portion!" );
3030         if ( pNode != aPaM.GetNode() )
3031         {
3032             nY += pPortion->GetHeight();
3033         }
3034         else
3035         {
3036             aEditCursor = GetEditCursor( pPortion, aPaM.GetIndex(), nFlags );
3037             aEditCursor.AdjustTop(nY );
3038             aEditCursor.AdjustBottom(nY );
3039             return aEditCursor;
3040         }
3041     }
3042     OSL_FAIL( "Portion not found!" );
3043     return aEditCursor;
3044 }
3045 
3046 EditPaM ImpEditEngine::GetPaM( Point aDocPos, bool bSmart )
3047 {
3048     OSL_ENSURE( GetUpdateMode(), "Must not be reached when Update=FALSE: GetPaM" );
3049 
3050     long nY = 0;
3051     EditPaM aPaM;
3052     sal_Int32 nPortion;
3053     for ( nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ )
3054     {
3055         ParaPortion* pPortion = GetParaPortions()[nPortion];
3056         const long nTmpHeight = pPortion->GetHeight();     // should also be correct for !bVisible!
3057         nY += nTmpHeight;
3058         if ( nY > aDocPos.Y() )
3059         {
3060             nY -= nTmpHeight;
3061             aDocPos.AdjustY( -nY );
3062             // Skip invisible Portions:
3063             while ( pPortion && !pPortion->IsVisible() )
3064             {
3065                 nPortion++;
3066                 pPortion = GetParaPortions().SafeGetObject( nPortion );
3067             }
3068             SAL_WARN_IF(!pPortion, "editeng", "worrying lack of any visible paragraph");
3069             if (!pPortion)
3070                 return aPaM;
3071             return GetPaM(pPortion, aDocPos, bSmart);
3072 
3073         }
3074     }
3075     // Then search for the last visible:
3076     nPortion = GetParaPortions().Count()-1;
3077     while ( nPortion && !GetParaPortions()[nPortion]->IsVisible() )
3078         nPortion--;
3079 
3080     OSL_ENSURE( GetParaPortions()[nPortion]->IsVisible(), "No visible paragraph found: GetPaM" );
3081     aPaM.SetNode( GetParaPortions()[nPortion]->GetNode() );
3082     aPaM.SetIndex( GetParaPortions()[nPortion]->GetNode()->Len() );
3083     return aPaM;
3084 }
3085 
3086 sal_uInt32 ImpEditEngine::GetTextHeight() const
3087 {
3088     OSL_ENSURE( GetUpdateMode(), "Should not be used for Update=FALSE: GetTextHeight" );
3089     OSL_ENSURE( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" );
3090     return nCurTextHeight;
3091 }
3092 
3093 sal_uInt32 ImpEditEngine::CalcTextWidth( bool bIgnoreExtraSpace )
3094 {
3095     // If still not formatted and not in the process.
3096     // Will be brought in the formatting for AutoPageSize.
3097     if ( !IsFormatted() && !IsFormatting() )
3098         FormatDoc();
3099 
3100     sal_uInt32 nMaxWidth = 0;
3101 
3102     // Over all the paragraphs ...
3103 
3104     sal_Int32 nParas = GetParaPortions().Count();
3105     for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ )
3106     {
3107         nMaxWidth = std::max(nMaxWidth, CalcParaWidth(nPara, bIgnoreExtraSpace));
3108     }
3109 
3110     return nMaxWidth;
3111 }
3112 
3113 sal_uInt32 ImpEditEngine::CalcParaWidth( sal_Int32 nPara, bool bIgnoreExtraSpace )
3114 {
3115     // If still not formatted and not in the process.
3116     // Will be brought in the formatting for AutoPageSize.
3117     if ( !IsFormatted() && !IsFormatting() )
3118         FormatDoc();
3119 
3120     long nMaxWidth = 0;
3121 
3122     // Over all the paragraphs ...
3123 
3124     OSL_ENSURE( 0 <= nPara && nPara < GetParaPortions().Count(), "CalcParaWidth: Out of range" );
3125     ParaPortion* pPortion = GetParaPortions()[nPara];
3126     if ( pPortion && pPortion->IsVisible() )
3127     {
3128         const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pPortion->GetNode() );
3129         sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pPortion->GetNode() );
3130 
3131 
3132         // On the lines of the paragraph ...
3133 
3134         sal_Int32 nLines = pPortion->GetLines().Count();
3135         for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ )
3136         {
3137             EditLine& rLine = pPortion->GetLines()[nLine];
3138             // nCurWidth = pLine->GetStartPosX();
3139             // For Center- or Right- alignment it depends on the paper
3140             // width, here not preferred. I general, it is best not leave it
3141             // to StartPosX, also the right indents have to be taken into
3142             // account!
3143             long nCurWidth = GetXValue( rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth );
3144             if ( nLine == 0 )
3145             {
3146                 long nFI = GetXValue( rLRItem.GetTextFirstLineOfst() );
3147                 nCurWidth -= nFI;
3148                 if ( pPortion->GetBulletX() > nCurWidth )
3149                 {
3150                     nCurWidth += nFI;   // LI?
3151                     if ( pPortion->GetBulletX() > nCurWidth )
3152                         nCurWidth = pPortion->GetBulletX();
3153                 }
3154             }
3155             nCurWidth += GetXValue( rLRItem.GetRight() );
3156             nCurWidth += CalcLineWidth( pPortion, &rLine, bIgnoreExtraSpace );
3157             if ( nCurWidth > nMaxWidth )
3158             {
3159                 nMaxWidth = nCurWidth;
3160             }
3161         }
3162     }
3163 
3164     nMaxWidth++; // widen it, because in CreateLines for >= is wrapped.
3165     return static_cast<sal_uInt32>(nMaxWidth);
3166 }
3167 
3168 sal_uInt32 ImpEditEngine::CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, bool bIgnoreExtraSpace )
3169 {
3170     sal_Int32 nPara = GetEditDoc().GetPos( pPortion->GetNode() );
3171 
3172     // #114278# Saving both layout mode and language (since I'm
3173     // potentially changing both)
3174     GetRefDevice()->Push( PushFlags::TEXTLAYOUTMODE|PushFlags::TEXTLANGUAGE );
3175 
3176     ImplInitLayoutMode( GetRefDevice(), nPara, -1 );
3177 
3178     SvxAdjust eJustification = GetJustification( nPara );
3179 
3180     // Calculation of the width without the Indents ...
3181     sal_uInt32 nWidth = 0;
3182     sal_Int32 nPos = pLine->GetStart();
3183     for ( sal_Int32 nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ )
3184     {
3185         const TextPortion& rTextPortion = pPortion->GetTextPortions()[nTP];
3186         switch ( rTextPortion.GetKind() )
3187         {
3188             case PortionKind::FIELD:
3189             case PortionKind::HYPHENATOR:
3190             case PortionKind::TAB:
3191             {
3192                 nWidth += rTextPortion.GetSize().Width();
3193             }
3194             break;
3195             case PortionKind::TEXT:
3196             {
3197                 if ( ( eJustification != SvxAdjust::Block ) || ( !bIgnoreExtraSpace ) )
3198                 {
3199                     nWidth += rTextPortion.GetSize().Width();
3200                 }
3201                 else
3202                 {
3203                     SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() );
3204                     SeekCursor( pPortion->GetNode(), nPos+1, aTmpFont );
3205                     aTmpFont.SetPhysFont( GetRefDevice() );
3206                     ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage());
3207                     nWidth += aTmpFont.QuickGetTextSize( GetRefDevice(), pPortion->GetNode()->GetString(), nPos, rTextPortion.GetLen() ).Width();
3208                 }
3209             }
3210             break;
3211             case PortionKind::LINEBREAK: break;
3212         }
3213         nPos = nPos + rTextPortion.GetLen();
3214     }
3215 
3216     GetRefDevice()->Pop();
3217 
3218     return nWidth;
3219 }
3220 
3221 sal_uInt32 ImpEditEngine::GetTextHeightNTP() const
3222 {
3223     DBG_ASSERT( GetUpdateMode(), "Should not be used for Update=FALSE: GetTextHeight" );
3224     DBG_ASSERT( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" );
3225     return nCurTextHeightNTP;
3226 }
3227 
3228 sal_uInt32 ImpEditEngine::CalcTextHeight( sal_uInt32* pHeightNTP )
3229 {
3230     OSL_ENSURE( GetUpdateMode(), "Should not be used when Update=FALSE: CalcTextHeight" );
3231     sal_uInt32 nY = 0;
3232     sal_uInt32 nPH;
3233     sal_uInt32 nEmptyHeight = 0;
3234     for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) {
3235         ParaPortion* pPortion = GetParaPortions()[nPortion];
3236         nPH = pPortion->GetHeight();
3237         nY += nPH;
3238         if( pHeightNTP ) {
3239             if ( pPortion->IsEmpty() )
3240                 nEmptyHeight += nPH;
3241             else
3242                 nEmptyHeight = 0;
3243         }
3244     }
3245 
3246     if ( pHeightNTP )
3247         *pHeightNTP = nY - nEmptyHeight;
3248 
3249     return nY;
3250 }
3251 
3252 sal_Int32 ImpEditEngine::GetLineCount( sal_Int32 nParagraph ) const
3253 {
3254     OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" );
3255     const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
3256     OSL_ENSURE( pPPortion, "Paragraph not found: GetLineCount" );
3257     if ( pPPortion )
3258         return pPPortion->GetLines().Count();
3259 
3260     return -1;
3261 }
3262 
3263 sal_Int32 ImpEditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const
3264 {
3265     OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineLen: Out of range" );
3266     const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
3267     OSL_ENSURE( pPPortion, "Paragraph not found: GetLineLen" );
3268     if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) )
3269     {
3270         const EditLine& rLine = pPPortion->GetLines()[nLine];
3271         return rLine.GetLen();
3272     }
3273 
3274     return -1;
3275 }
3276 
3277 void ImpEditEngine::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const
3278 {
3279     OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" );
3280     const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
3281     OSL_ENSURE( pPPortion, "Paragraph not found: GetLineBoundaries" );
3282     rStart = rEnd = -1;     // default values in case of error
3283     if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) )
3284     {
3285         const EditLine& rLine = pPPortion->GetLines()[nLine];
3286         rStart = rLine.GetStart();
3287         rEnd   = rLine.GetEnd();
3288     }
3289 }
3290 
3291 sal_Int32 ImpEditEngine::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const
3292 {
3293     sal_Int32 nLineNo = -1;
3294     const ContentNode* pNode = GetEditDoc().GetObject( nPara );
3295     OSL_ENSURE( pNode, "GetLineNumberAtIndex: invalid paragraph index" );
3296     if (pNode)
3297     {
3298         // we explicitly allow for the index to point at the character right behind the text
3299         const bool bValidIndex = /*0 <= nIndex &&*/ nIndex <= pNode->Len();
3300         OSL_ENSURE( bValidIndex, "GetLineNumberAtIndex: invalid index" );
3301         const sal_Int32 nLineCount = GetLineCount( nPara );
3302         if (nIndex == pNode->Len())
3303             nLineNo = nLineCount > 0 ? nLineCount - 1 : 0;
3304         else if (bValidIndex)   // nIndex < pNode->Len()
3305         {
3306             sal_Int32 nStart = -1, nEnd = -1;
3307             for (sal_Int32 i = 0;  i < nLineCount && nLineNo == -1;  ++i)
3308             {
3309                 GetLineBoundaries( nStart, nEnd, nPara, i );
3310                 if (nStart >= 0 && nStart <= nIndex && nEnd >= 0 && nIndex < nEnd)
3311                     nLineNo = i;
3312             }
3313         }
3314     }
3315     return nLineNo;
3316 }
3317 
3318 sal_uInt16 ImpEditEngine::GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine )
3319 {
3320     OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" );
3321     ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
3322     OSL_ENSURE( pPPortion, "Paragraph not found: GetLineHeight" );
3323     if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) )
3324     {
3325         const EditLine& rLine = pPPortion->GetLines()[nLine];
3326         return rLine.GetHeight();
3327     }
3328 
3329     return 0xFFFF;
3330 }
3331 
3332 sal_uInt32 ImpEditEngine::GetParaHeight( sal_Int32 nParagraph )
3333 {
3334     sal_uInt32 nHeight = 0;
3335 
3336     ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
3337     OSL_ENSURE( pPPortion, "Paragraph not found: GetParaHeight" );
3338 
3339     if ( pPPortion )
3340         nHeight = pPPortion->GetHeight();
3341 
3342     return nHeight;
3343 }
3344 
3345 void ImpEditEngine::UpdateSelections()
3346 {
3347     // Check whether one of the selections is at a deleted node...
3348     // If the node is valid, the index has yet to be examined!
3349     for (EditView* pView : aEditViews)
3350     {
3351         EditSelection aCurSel( pView->pImpEditView->GetEditSelection() );
3352         bool bChanged = false;
3353         for (std::unique_ptr<DeletedNodeInfo> & aDeletedNode : aDeletedNodes)
3354         {
3355             const DeletedNodeInfo& rInf = *aDeletedNode;
3356             if ( ( aCurSel.Min().GetNode() == rInf.GetNode() ) ||
3357                  ( aCurSel.Max().GetNode() == rInf.GetNode() ) )
3358             {
3359                 // Use ParaPortions, as now also hidden paragraphs have to be
3360                 // taken into account!
3361                 sal_Int32 nPara = rInf.GetPosition();
3362                 if (!GetParaPortions().SafeGetObject(nPara)) // Last paragraph
3363                 {
3364                     nPara = GetParaPortions().Count()-1;
3365                 }
3366                 assert(GetParaPortions()[nPara] && "Empty Document in UpdateSelections ?");
3367                 // Do not end up from a hidden paragraph:
3368                 sal_Int32 nCurPara = nPara;
3369                 sal_Int32 nLastPara = GetParaPortions().Count()-1;
3370                 while ( nPara <= nLastPara && !GetParaPortions()[nPara]->IsVisible() )
3371                     nPara++;
3372                 if ( nPara > nLastPara ) // then also backwards ...
3373                 {
3374                     nPara = nCurPara;
3375                     while ( nPara && !GetParaPortions()[nPara]->IsVisible() )
3376                         nPara--;
3377                 }
3378                 OSL_ENSURE( GetParaPortions()[nPara]->IsVisible(), "No visible paragraph found: UpdateSelections" );
3379 
3380                 ParaPortion* pParaPortion = GetParaPortions()[nPara];
3381                 EditSelection aTmpSelection( EditPaM( pParaPortion->GetNode(), 0 ) );
3382                 pView->pImpEditView->SetEditSelection( aTmpSelection );
3383                 bChanged=true;
3384                 break;  // for loop
3385             }
3386         }
3387         if ( !bChanged )
3388         {
3389             // Check Index if node shrunk.
3390             if ( aCurSel.Min().GetIndex() > aCurSel.Min().GetNode()->Len() )
3391             {
3392                 aCurSel.Min().SetIndex( aCurSel.Min().GetNode()->Len() );
3393                 pView->pImpEditView->SetEditSelection( aCurSel );
3394             }
3395             if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() )
3396             {
3397                 aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() );
3398                 pView->pImpEditView->SetEditSelection( aCurSel );
3399             }
3400         }
3401     }
3402     aDeletedNodes.clear();
3403 }
3404 
3405 EditSelection ImpEditEngine::ConvertSelection(
3406     sal_Int32 nStartPara, sal_Int32 nStartPos, sal_Int32 nEndPara, sal_Int32 nEndPos )
3407 {
3408     EditSelection aNewSelection;
3409 
3410     // Start...
3411     ContentNode* pNode = aEditDoc.GetObject( nStartPara );
3412     sal_Int32 nIndex = nStartPos;
3413     if ( !pNode )
3414     {
3415         pNode = aEditDoc[ aEditDoc.Count()-1 ];
3416         nIndex = pNode->Len();
3417     }
3418     else if ( nIndex > pNode->Len() )
3419         nIndex = pNode->Len();
3420 
3421     aNewSelection.Min().SetNode( pNode );
3422     aNewSelection.Min().SetIndex( nIndex );
3423 
3424     // End...
3425     pNode = aEditDoc.GetObject( nEndPara );
3426     nIndex = nEndPos;
3427     if ( !pNode )
3428     {
3429         pNode = aEditDoc[ aEditDoc.Count()-1 ];
3430         nIndex = pNode->Len();
3431     }
3432     else if ( nIndex > pNode->Len() )
3433         nIndex = pNode->Len();
3434 
3435     aNewSelection.Max().SetNode( pNode );
3436     aNewSelection.Max().SetIndex( nIndex );
3437 
3438     return aNewSelection;
3439 }
3440 
3441 void ImpEditEngine::SetActiveView( EditView* pView )
3442 {
3443     // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
3444     // Actually, now bHasVisSel and HideSelection would be necessary     !!!
3445 
3446     if ( pView == pActiveView )
3447         return;
3448 
3449     if ( pActiveView && pActiveView->HasSelection() )
3450         pActiveView->pImpEditView->DrawSelectionXOR();
3451 
3452     pActiveView = pView;
3453 
3454     if ( pActiveView && pActiveView->HasSelection() )
3455         pActiveView->pImpEditView->DrawSelectionXOR();
3456 
3457     //  NN: Quick fix for #78668#:
3458     //  When editing of a cell in Calc is ended, the edit engine is not deleted,
3459     //  only the edit views are removed. If mpIMEInfos is still set in that case,
3460     //  mpIMEInfos->aPos points to an invalid selection.
3461     //  -> reset mpIMEInfos now
3462     //  (probably something like this is necessary whenever the content is modified
3463     //  from the outside)
3464 
3465     if ( !pView && mpIMEInfos )
3466     {
3467         mpIMEInfos.reset();
3468     }
3469 }
3470 
3471 uno::Reference< datatransfer::XTransferable > ImpEditEngine::CreateTransferable( const EditSelection& rSelection )
3472 {
3473     EditSelection aSelection( rSelection );
3474     aSelection.Adjust( GetEditDoc() );
3475 
3476     EditDataObject* pDataObj = new EditDataObject;
3477     uno::Reference< datatransfer::XTransferable > xDataObj = pDataObj;
3478 
3479     pDataObj->GetString() = convertLineEnd(GetSelected(aSelection), GetSystemLineEnd()); // System specific
3480 
3481     WriteRTF( pDataObj->GetRTFStream(), aSelection );
3482     pDataObj->GetRTFStream().Seek( 0 );
3483 
3484     WriteXML( pDataObj->GetODFStream(), aSelection );
3485     pDataObj->GetODFStream().Seek( 0 );
3486 
3487     //Dumping the ODFStream to a XML file for testing purpose
3488     /*
3489     std::filebuf afilebuf;
3490     afilebuf.open ("gsoc17_clipboard_test.xml",std::ios::out);
3491     std::ostream os(&afilebuf);
3492     os.write((const char*)(pDataObj->GetODFStream().GetBuffer()), pDataObj->GetODFStream().remainingSize());
3493     afilebuf.close();
3494     */
3495     //dumping ends
3496 
3497     if ( ( aSelection.Min().GetNode() == aSelection.Max().GetNode() )
3498             && ( aSelection.Max().GetIndex() == (aSelection.Min().GetIndex()+1) ) )
3499     {
3500         const EditCharAttrib* pAttr = aSelection.Min().GetNode()->GetCharAttribs().
3501             FindFeature( aSelection.Min().GetIndex() );
3502         if ( pAttr &&
3503             ( pAttr->GetStart() == aSelection.Min().GetIndex() ) &&
3504             ( pAttr->Which() == EE_FEATURE_FIELD ) )
3505         {
3506             const SvxFieldItem* pField = static_cast<const SvxFieldItem*>(pAttr->GetItem());
3507             const SvxFieldData* pFld = pField->GetField();
3508             if ( auto pUrlField = dynamic_cast<const SvxURLField* >(pFld) )
3509             {
3510                 // Office-Bookmark
3511                 pDataObj->GetURL() = pUrlField->GetURL();
3512             }
3513         }
3514     }
3515 
3516     return xDataObj;
3517 }
3518 
3519 EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial )
3520 {
3521     EditSelection aNewSelection( rPaM );
3522 
3523     if ( !rxDataObj.is() )
3524         return aNewSelection;
3525 
3526     datatransfer::DataFlavor aFlavor;
3527     bool bDone = false;
3528 
3529     if ( bUseSpecial )
3530     {
3531         // XML
3532         SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT, aFlavor );
3533         if ( rxDataObj->isDataFlavorSupported( aFlavor ) )
3534         {
3535             try
3536             {
3537                 uno::Any aData = rxDataObj->getTransferData( aFlavor );
3538                 uno::Sequence< sal_Int8 > aSeq;
3539                 aData >>= aSeq;
3540                 {
3541                     SvMemoryStream aODFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ );
3542                     aNewSelection = Read( aODFStream, rBaseURL, EETextFormat::Xml, rPaM );
3543                 }
3544                 bDone = true;
3545             }
3546             catch( const css::uno::Exception&)
3547             {
3548                 TOOLS_WARN_EXCEPTION( "editeng", "Unable to paste EDITENGINE_ODF_TEXT_FLAT" );
3549             }
3550         }
3551 
3552         if ( !bDone )
3553         {
3554             // RTF
3555             SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RTF, aFlavor );
3556             // RICHTEXT
3557             datatransfer::DataFlavor aFlavorRichtext;
3558             SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RICHTEXT, aFlavorRichtext );
3559             bool bRtfSupported = rxDataObj->isDataFlavorSupported( aFlavor );
3560             bool bRichtextSupported  = rxDataObj->isDataFlavorSupported( aFlavorRichtext );
3561             if ( bRtfSupported || bRichtextSupported )
3562             {
3563                 if(bRichtextSupported)
3564                 {
3565                     aFlavor = aFlavorRichtext;
3566                 }
3567                 try
3568                 {
3569                     uno::Any aData = rxDataObj->getTransferData( aFlavor );
3570                     uno::Sequence< sal_Int8 > aSeq;
3571                     aData >>= aSeq;
3572                     {
3573                         SvMemoryStream aRTFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ );
3574                         aNewSelection = Read( aRTFStream, rBaseURL, EETextFormat::Rtf, rPaM );
3575                     }
3576                     bDone = true;
3577                 }
3578                 catch( const css::uno::Exception& )
3579                 {
3580                 }
3581             }
3582         }
3583     }
3584     if ( !bDone )
3585     {
3586         SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
3587         if ( rxDataObj->isDataFlavorSupported( aFlavor ) )
3588         {
3589             try
3590             {
3591                 uno::Any aData = rxDataObj->getTransferData( aFlavor );
3592                 OUString aText;
3593                 aData >>= aText;
3594                 aNewSelection = ImpInsertText( rPaM, aText );
3595             }
3596             catch( ... )
3597             {
3598                 ; // #i9286# can happen, even if isDataFlavorSupported returns true...
3599             }
3600         }
3601     }
3602 
3603     return aNewSelection;
3604 }
3605 
3606 Range ImpEditEngine::GetInvalidYOffsets( ParaPortion* pPortion )
3607 {
3608     Range aRange( 0, 0 );
3609 
3610     if ( pPortion->IsVisible() )
3611     {
3612         const SvxULSpaceItem& rULSpace = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
3613         const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
3614         sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
3615                             ? GetYValue( rLSItem.GetInterLineSpace() ) : 0;
3616 
3617         // only from the top ...
3618         sal_Int32 nFirstInvalid = -1;
3619         sal_Int32 nLine;
3620         for ( nLine = 0; nLine < pPortion->GetLines().Count(); nLine++ )
3621         {
3622             const EditLine& rL = pPortion->GetLines()[nLine];
3623             if ( rL.IsInvalid() )
3624             {
3625                 nFirstInvalid = nLine;
3626                 break;
3627             }
3628             if ( nLine && !aStatus.IsOutliner() )   // not the first line
3629                 aRange.Min() += nSBL;
3630             aRange.Min() += rL.GetHeight();
3631         }
3632         OSL_ENSURE( nFirstInvalid != -1, "No invalid line found in GetInvalidYOffset(1)" );
3633 
3634 
3635         // Syndicate and more ...
3636         aRange.Max() = aRange.Min();
3637         aRange.Max() += pPortion->GetFirstLineOffset();
3638         if (nFirstInvalid >= 0)   // Only if the first line is invalid
3639             aRange.Min() = aRange.Max();
3640 
3641         sal_Int32 nLastInvalid = pPortion->GetLines().Count()-1;
3642         if (nFirstInvalid >= 0)
3643         {
3644             for ( nLine = nFirstInvalid; nLine < pPortion->GetLines().Count(); nLine++ )
3645             {
3646                 const EditLine& rL = pPortion->GetLines()[nLine];
3647                 if ( rL.IsValid() )
3648                 {
3649                     nLastInvalid = nLine;
3650                     break;
3651                 }
3652                 if ( nLine && !aStatus.IsOutliner() )
3653                     aRange.Max() += nSBL;
3654                 aRange.Max() += rL.GetHeight();
3655             }
3656 
3657             sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace();
3658             if ( ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
3659                 && nPropLineSpace && ( nPropLineSpace < 100 ) )
3660             {
3661                 const EditLine& rL = pPortion->GetLines()[nFirstInvalid];
3662                 long n = rL.GetTxtHeight() * ( 100L - nPropLineSpace );
3663                 n /= 100;
3664                 aRange.Min() -= n;
3665                 aRange.Max() += n;
3666             }
3667 
3668             if ( ( nLastInvalid == pPortion->GetLines().Count()-1 ) && ( !aStatus.IsOutliner() ) )
3669                 aRange.Max() += GetYValue( rULSpace.GetLower() );
3670         }
3671     }
3672     return aRange;
3673 }
3674 
3675 EditPaM ImpEditEngine::GetPaM( ParaPortion* pPortion, Point aDocPos, bool bSmart )
3676 {
3677     OSL_ENSURE( pPortion->IsVisible(), "Why GetPaM() for an invisible paragraph?" );
3678     OSL_ENSURE( IsFormatted(), "GetPaM: Not formatted" );
3679 
3680     sal_Int32 nCurIndex = 0;
3681     EditPaM aPaM;
3682     aPaM.SetNode( pPortion->GetNode() );
3683 
3684     const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
3685     sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
3686                         ? GetYValue( rLSItem.GetInterLineSpace() ) : 0;
3687 
3688     long nY = pPortion->GetFirstLineOffset();
3689 
3690     OSL_ENSURE( pPortion->GetLines().Count(), "Empty ParaPortion in GetPaM!" );
3691 
3692     const EditLine* pLine = nullptr;
3693     for ( sal_Int32 nLine = 0; nLine < pPortion->GetLines().Count(); nLine++ )
3694     {
3695         const EditLine& rTmpLine = pPortion->GetLines()[nLine];
3696         nY += rTmpLine.GetHeight();
3697         if ( !aStatus.IsOutliner() )
3698             nY += nSBL;
3699         if ( nY > aDocPos.Y() )
3700         {
3701             pLine = &rTmpLine;
3702             break;                  // correct Y-position is not of interest
3703         }
3704 
3705         nCurIndex = nCurIndex + rTmpLine.GetLen();
3706     }
3707 
3708     if ( !pLine ) // may happen only in the range of SA!
3709     {
3710 #if OSL_DEBUG_LEVEL > 0
3711         const SvxULSpaceItem& rULSpace = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
3712         OSL_ENSURE( nY+GetYValue( rULSpace.GetLower() ) >= aDocPos.Y() , "Index in no line, GetPaM ?" );
3713 #endif
3714         aPaM.SetIndex( pPortion->GetNode()->Len() );
3715         return aPaM;
3716     }
3717 
3718     // If no line found, only just X-Position => Index
3719     nCurIndex = GetChar( pPortion, pLine, aDocPos.X(), bSmart );
3720     aPaM.SetIndex( nCurIndex );
3721 
3722     if ( nCurIndex && ( nCurIndex == pLine->GetEnd() ) &&
3723          ( pLine != &pPortion->GetLines()[pPortion->GetLines().Count()-1] ) )
3724     {
3725         aPaM = CursorLeft( aPaM );
3726     }
3727 
3728     return aPaM;
3729 }
3730 
3731 sal_Int32 ImpEditEngine::GetChar(
3732     const ParaPortion* pParaPortion, const EditLine* pLine, long nXPos, bool bSmart)
3733 {
3734     OSL_ENSURE( pLine, "No line received: GetChar" );
3735 
3736     sal_Int32 nChar = -1;
3737     sal_Int32 nCurIndex = pLine->GetStart();
3738 
3739 
3740     // Search best matching portion with GetPortionXOffset()
3741     for ( sal_Int32 i = pLine->GetStartPortion(); i <= pLine->GetEndPortion(); i++ )
3742     {
3743         const TextPortion& rPortion = pParaPortion->GetTextPortions()[i];
3744         long nXLeft = GetPortionXOffset( pParaPortion, pLine, i );
3745         long nXRight = nXLeft + rPortion.GetSize().Width();
3746         if ( ( nXLeft <= nXPos ) && ( nXRight >= nXPos ) )
3747         {
3748             nChar = nCurIndex;
3749 
3750             // Search within Portion...
3751 
3752             // Don't search within special portions...
3753             if ( rPortion.GetKind() != PortionKind::TEXT )
3754             {
3755                 // ...but check on which side
3756                 if ( bSmart )
3757                 {
3758                     long nLeftDiff = nXPos-nXLeft;
3759                     long nRightDiff = nXRight-nXPos;
3760                     if ( nRightDiff < nLeftDiff )
3761                         nChar++;
3762                 }
3763             }
3764             else
3765             {
3766                 sal_Int32 nMax = rPortion.GetLen();
3767                 sal_Int32 nOffset = -1;
3768                 sal_Int32 nTmpCurIndex = nChar - pLine->GetStart();
3769 
3770                 long nXInPortion = nXPos - nXLeft;
3771                 if ( rPortion.IsRightToLeft() )
3772                     nXInPortion = nXRight - nXPos;
3773 
3774                 // Search in Array...
3775                 for ( sal_Int32 x = 0; x < nMax; x++ )
3776                 {
3777                     long nTmpPosMax = pLine->GetCharPosArray()[nTmpCurIndex+x];
3778                     if ( nTmpPosMax > nXInPortion )
3779                     {
3780                         // Check whether this or the previous...
3781                         long nTmpPosMin = x ? pLine->GetCharPosArray()[nTmpCurIndex+x-1] : 0;
3782                         long nDiffLeft = nXInPortion - nTmpPosMin;
3783                         long nDiffRight = nTmpPosMax - nXInPortion;
3784                         OSL_ENSURE( nDiffLeft >= 0, "DiffLeft negative" );
3785                         OSL_ENSURE( nDiffRight >= 0, "DiffRight negative" );
3786                         nOffset = ( bSmart && ( nDiffRight < nDiffLeft ) ) ? x+1 : x;
3787                         // I18N: If there are character position with the length of 0,
3788                         // they belong to the same character, we can not use this position as an index.
3789                         // Skip all 0-positions, cheaper than using XBreakIterator:
3790                         if ( nOffset < nMax )
3791                         {
3792                             const long nX = pLine->GetCharPosArray()[nOffset];
3793                             while ( ( (nOffset+1) < nMax ) && ( pLine->GetCharPosArray()[nOffset+1] == nX ) )
3794                                 nOffset++;
3795                         }
3796                         break;
3797                     }
3798                 }
3799 
3800                 // There should not be any inaccuracies when using the
3801                 // CharPosArray! Maybe for kerning?
3802                 // 0xFFF happens for example for Outline-Font when at the very end.
3803                 if ( nOffset < 0 )
3804                     nOffset = nMax;
3805 
3806                 OSL_ENSURE( nOffset <= nMax, "nOffset > nMax" );
3807 
3808                 nChar = nChar + nOffset;
3809 
3810                 // Check if index is within a cell:
3811                 if ( nChar && ( nChar < pParaPortion->GetNode()->Len() ) )
3812                 {
3813                     EditPaM aPaM( pParaPortion->GetNode(), nChar+1 );
3814                     sal_uInt16 nScriptType = GetI18NScriptType( aPaM );
3815                     if ( nScriptType == i18n::ScriptType::COMPLEX )
3816                     {
3817                         uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
3818                         sal_Int32 nCount = 1;
3819                         lang::Locale aLocale = GetLocale( aPaM );
3820                         sal_Int32 nRight = _xBI->nextCharacters(
3821                             pParaPortion->GetNode()->GetString(), nChar, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
3822                         sal_Int32 nLeft = _xBI->previousCharacters(
3823                             pParaPortion->GetNode()->GetString(), nRight, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
3824                         if ( ( nLeft != nChar ) && ( nRight != nChar ) )
3825                         {
3826                             nChar = ( std::abs( nRight - nChar ) < std::abs( nLeft - nChar ) ) ? nRight : nLeft;
3827                         }
3828                     }
3829                 }
3830             }
3831         }
3832 
3833         nCurIndex = nCurIndex + rPortion.GetLen();
3834     }
3835 
3836     if ( nChar == -1 )
3837     {
3838         nChar = ( nXPos <= pLine->GetStartPosX() ) ? pLine->GetStart() : pLine->GetEnd();
3839     }
3840 
3841     return nChar;
3842 }
3843 
3844 Range ImpEditEngine::GetLineXPosStartEnd( const ParaPortion* pParaPortion, const EditLine* pLine ) const
3845 {
3846     Range aLineXPosStartEnd;
3847 
3848     sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() );
3849     if ( !IsRightToLeft( nPara ) )
3850     {
3851         aLineXPosStartEnd.Min() = pLine->GetStartPosX();
3852         aLineXPosStartEnd.Max() = pLine->GetStartPosX() + pLine->GetTextWidth();
3853     }
3854     else
3855     {
3856         aLineXPosStartEnd.Min() = GetPaperSize().Width() - ( pLine->GetStartPosX() + pLine->GetTextWidth() );
3857         aLineXPosStartEnd.Max() = GetPaperSize().Width() - pLine->GetStartPosX();
3858     }
3859 
3860 
3861     return aLineXPosStartEnd;
3862 }
3863 
3864 long ImpEditEngine::GetPortionXOffset(
3865     const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nTextPortion) const
3866 {
3867     long nX = pLine->GetStartPosX();
3868 
3869     for ( sal_Int32 i = pLine->GetStartPortion(); i < nTextPortion; i++ )
3870     {
3871         const TextPortion& rPortion = pParaPortion->GetTextPortions()[i];
3872         switch ( rPortion.GetKind() )
3873         {
3874             case PortionKind::FIELD:
3875             case PortionKind::TEXT:
3876             case PortionKind::HYPHENATOR:
3877             case PortionKind::TAB:
3878             {
3879                 nX += rPortion.GetSize().Width();
3880             }
3881             break;
3882             case PortionKind::LINEBREAK: break;
3883         }
3884     }
3885 
3886     sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() );
3887     bool bR2LPara = IsRightToLeft( nPara );
3888 
3889     const TextPortion& rDestPortion = pParaPortion->GetTextPortions()[nTextPortion];
3890     if ( rDestPortion.GetKind() != PortionKind::TAB )
3891     {
3892         if ( !bR2LPara && rDestPortion.GetRightToLeftLevel() )
3893         {
3894             // Portions behind must be added, visual before this portion
3895             sal_Int32 nTmpPortion = nTextPortion+1;
3896             while ( nTmpPortion <= pLine->GetEndPortion() )
3897             {
3898                 const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion];
3899                 if ( rNextTextPortion.GetRightToLeftLevel() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) )
3900                     nX += rNextTextPortion.GetSize().Width();
3901                 else
3902                     break;
3903                 nTmpPortion++;
3904             }
3905             // Portions before must be removed, visual behind this portion
3906             nTmpPortion = nTextPortion;
3907             while ( nTmpPortion > pLine->GetStartPortion() )
3908             {
3909                 --nTmpPortion;
3910                 const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion];
3911                 if ( rPrevTextPortion.GetRightToLeftLevel() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) )
3912                     nX -= rPrevTextPortion.GetSize().Width();
3913                 else
3914                     break;
3915             }
3916         }
3917         else if ( bR2LPara && !rDestPortion.IsRightToLeft() )
3918         {
3919             // Portions behind must be removed, visual behind this portion
3920             sal_Int32 nTmpPortion = nTextPortion+1;
3921             while ( nTmpPortion <= pLine->GetEndPortion() )
3922             {
3923                 const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion];
3924                 if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) )
3925                     nX += rNextTextPortion.GetSize().Width();
3926                 else
3927                     break;
3928                 nTmpPortion++;
3929             }
3930             // Portions before must be added, visual before this portion
3931             nTmpPortion = nTextPortion;
3932             while ( nTmpPortion > pLine->GetStartPortion() )
3933             {
3934                 --nTmpPortion;
3935                 const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion];
3936                 if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) )
3937                     nX -= rPrevTextPortion.GetSize().Width();
3938                 else
3939                     break;
3940             }
3941         }
3942     }
3943     if ( bR2LPara )
3944     {
3945         // Switch X positions...
3946         OSL_ENSURE( GetTextRanger() || GetPaperSize().Width(), "GetPortionXOffset - paper size?!" );
3947         OSL_ENSURE( GetTextRanger() || (nX <= GetPaperSize().Width()), "GetPortionXOffset - position out of paper size!" );
3948         nX = GetPaperSize().Width() - nX;
3949         nX -= rDestPortion.GetSize().Width();
3950     }
3951 
3952     return nX;
3953 }
3954 
3955 long ImpEditEngine::GetXPos(
3956     const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart) const
3957 {
3958     OSL_ENSURE( pLine, "No line received: GetXPos" );
3959     OSL_ENSURE( ( nIndex >= pLine->GetStart() ) && ( nIndex <= pLine->GetEnd() ) , "GetXPos has to be called properly!" );
3960 
3961     bool bDoPreferPortionStart = bPreferPortionStart;
3962     // Assure that the portion belongs to this line:
3963     if ( nIndex == pLine->GetStart() )
3964         bDoPreferPortionStart = true;
3965     else if ( nIndex == pLine->GetEnd() )
3966         bDoPreferPortionStart = false;
3967 
3968     sal_Int32 nTextPortionStart = 0;
3969     sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart );
3970 
3971     OSL_ENSURE( ( nTextPortion >= pLine->GetStartPortion() ) && ( nTextPortion <= pLine->GetEndPortion() ), "GetXPos: Portion not in current line! " );
3972 
3973     const TextPortion& rPortion = pParaPortion->GetTextPortions()[nTextPortion];
3974 
3975     long nX = GetPortionXOffset( pParaPortion, pLine, nTextPortion );
3976 
3977     // calc text width, portion size may include CJK/CTL spacing...
3978     // But the array might not be init yet, if using text ranger this method is called within CreateLines()...
3979     long nPortionTextWidth = rPortion.GetSize().Width();
3980     if ( ( rPortion.GetKind() == PortionKind::TEXT ) && rPortion.GetLen() && !GetTextRanger() )
3981         nPortionTextWidth = pLine->GetCharPosArray()[nTextPortionStart + rPortion.GetLen() - 1 - pLine->GetStart()];
3982 
3983     if ( nTextPortionStart != nIndex )
3984     {
3985         // Search within portion...
3986         if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) )
3987         {
3988             // End of Portion
3989             if ( rPortion.GetKind() == PortionKind::TAB )
3990             {
3991                 if ( nTextPortion+1 < pParaPortion->GetTextPortions().Count() )
3992                 {
3993                     const TextPortion& rNextPortion = pParaPortion->GetTextPortions()[nTextPortion+1];
3994                     if ( rNextPortion.GetKind() != PortionKind::TAB )
3995                     {
3996                         if ( !bPreferPortionStart )
3997                             nX = GetXPos( pParaPortion, pLine, nIndex, true );
3998                         else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) )
3999                             nX += nPortionTextWidth;
4000                     }
4001                 }
4002                 else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) )
4003                 {
4004                     nX += nPortionTextWidth;
4005                 }
4006             }
4007             else if ( !rPortion.IsRightToLeft() )
4008             {
4009                 nX += nPortionTextWidth;
4010             }
4011         }
4012         else if ( rPortion.GetKind() == PortionKind::TEXT )
4013         {
4014             OSL_ENSURE( nIndex != pLine->GetStart(), "Strange behavior in new GetXPos()" );
4015             OSL_ENSURE( pLine && !pLine->GetCharPosArray().empty(), "svx::ImpEditEngine::GetXPos(), portion in an empty line?" );
4016 
4017             if( !pLine->GetCharPosArray().empty() )
4018             {
4019                 sal_Int32 nPos = nIndex - 1 - pLine->GetStart();
4020                 if (nPos < 0 || nPos >= static_cast<sal_Int32>(pLine->GetCharPosArray().size()))
4021                 {
4022                     nPos = pLine->GetCharPosArray().size()-1;
4023                     OSL_FAIL("svx::ImpEditEngine::GetXPos(), index out of range!");
4024                 }
4025 
4026                 // old code restored see #i112788 (which leaves #i74188 unfixed again)
4027                 long nPosInPortion = pLine->GetCharPosArray()[nPos];
4028 
4029                 if ( !rPortion.IsRightToLeft() )
4030                 {
4031                     nX += nPosInPortion;
4032                 }
4033                 else
4034                 {
4035                     nX += nPortionTextWidth - nPosInPortion;
4036                 }
4037 
4038                 if ( rPortion.GetExtraInfos() && rPortion.GetExtraInfos()->bCompressed )
4039                 {
4040                     nX += rPortion.GetExtraInfos()->nPortionOffsetX;
4041                     if ( rPortion.GetExtraInfos()->nAsianCompressionTypes & AsianCompressionFlags::PunctuationRight )
4042                     {
4043                         AsianCompressionFlags nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex ) );
4044                         if ( nType == AsianCompressionFlags::PunctuationRight && !pLine->GetCharPosArray().empty() )
4045                         {
4046                             sal_Int32 n = nIndex - nTextPortionStart;
4047                             const long* pDXArray = pLine->GetCharPosArray().data()+( nTextPortionStart-pLine->GetStart() );
4048                             sal_Int32 nCharWidth = ( ( (n+1) < rPortion.GetLen() ) ? pDXArray[n] : rPortion.GetSize().Width() )
4049                                                             - ( n ? pDXArray[n-1] : 0 );
4050                             if ( (n+1) < rPortion.GetLen() )
4051                             {
4052                                 // smaller, when char behind is AsianCompressionFlags::PunctuationRight also
4053                                 nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex+1 ) );
4054                                 if ( nType == AsianCompressionFlags::PunctuationRight )
4055                                 {
4056                                     sal_Int32 nNextCharWidth = ( ( (n+2) < rPortion.GetLen() ) ? pDXArray[n+1] : rPortion.GetSize().Width() )
4057                                                                     - pDXArray[n];
4058                                     sal_Int32 nCompressed = nNextCharWidth/2;
4059                                     nCompressed *= rPortion.GetExtraInfos()->nMaxCompression100thPercent;
4060                                     nCompressed /= 10000;
4061                                     nCharWidth += nCompressed;
4062                                 }
4063                             }
4064                             else
4065                             {
4066                                 nCharWidth *= 2;    // last char pos to portion end is only compressed size
4067                             }
4068                             nX += nCharWidth/2; // 50% compression
4069                         }
4070                     }
4071                 }
4072             }
4073         }
4074     }
4075     else // if ( nIndex == pLine->GetStart() )
4076     {
4077         if ( rPortion.IsRightToLeft() )
4078         {
4079             nX += nPortionTextWidth;
4080         }
4081     }
4082 
4083     return nX;
4084 }
4085 
4086 void ImpEditEngine::CalcHeight( ParaPortion* pPortion )
4087 {
4088     pPortion->nHeight = 0;
4089     pPortion->nFirstLineOffset = 0;
4090 
4091     if ( pPortion->IsVisible() )
4092     {
4093         OSL_ENSURE( pPortion->GetLines().Count(), "Paragraph with no lines in ParaPortion::CalcHeight" );
4094         for (sal_Int32 nLine = 0; nLine < pPortion->GetLines().Count(); ++nLine)
4095             pPortion->nHeight += pPortion->GetLines()[nLine].GetHeight();
4096 
4097         if ( !aStatus.IsOutliner() )
4098         {
4099             const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
4100             const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
4101             sal_Int32 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) ? GetYValue( rLSItem.GetInterLineSpace() ) : 0;
4102 
4103             if ( nSBL )
4104             {
4105                 if ( pPortion->GetLines().Count() > 1 )
4106                     pPortion->nHeight += ( pPortion->GetLines().Count() - 1 ) * nSBL;
4107                 if ( aStatus.ULSpaceSummation() )
4108                     pPortion->nHeight += nSBL;
4109             }
4110 
4111             sal_Int32 nPortion = GetParaPortions().GetPos( pPortion );
4112             if ( nPortion )
4113             {
4114                 sal_uInt16 nUpper = GetYValue( rULItem.GetUpper() );
4115                 pPortion->nHeight += nUpper;
4116                 pPortion->nFirstLineOffset = nUpper;
4117             }
4118 
4119             if ( nPortion != (GetParaPortions().Count()-1) )
4120             {
4121                 pPortion->nHeight += GetYValue( rULItem.GetLower() );   // not in the last
4122             }
4123 
4124 
4125             if ( nPortion && !aStatus.ULSpaceSummation() )
4126             {
4127                 ParaPortion* pPrev = GetParaPortions().SafeGetObject( nPortion-1 );
4128                 if (pPrev)
4129                 {
4130                     const SvxULSpaceItem& rPrevULItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
4131                     const SvxLineSpacingItem& rPrevLSItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
4132 
4133                     // In relation between WinWord6/Writer3:
4134                     // With a proportional line spacing the paragraph spacing is
4135                     // also manipulated.
4136                     // Only Writer3: Do not add up, but minimum distance.
4137 
4138                     // check if distance by LineSpacing > Upper:
4139                     sal_uInt16 nExtraSpace = GetYValue( lcl_CalcExtraSpace( rLSItem ) );
4140                     if ( nExtraSpace > pPortion->nFirstLineOffset )
4141                     {
4142                         // Paragraph becomes 'bigger':
4143                         pPortion->nHeight += ( nExtraSpace - pPortion->nFirstLineOffset );
4144                         pPortion->nFirstLineOffset = nExtraSpace;
4145                     }
4146 
4147                     // Determine nFirstLineOffset now f(pNode) => now f(pNode, pPrev):
4148                     sal_uInt16 nPrevLower = GetYValue( rPrevULItem.GetLower() );
4149 
4150                     // This PrevLower is still in the height of PrevPortion ...
4151                     if ( nPrevLower > pPortion->nFirstLineOffset )
4152                     {
4153                         // Paragraph is 'small':
4154                         pPortion->nHeight -= pPortion->nFirstLineOffset;
4155                         pPortion->nFirstLineOffset = 0;
4156                     }
4157                     else if ( nPrevLower )
4158                     {
4159                         // Paragraph becomes 'somewhat smaller':
4160                         pPortion->nHeight -= nPrevLower;
4161                         pPortion->nFirstLineOffset =
4162                             pPortion->nFirstLineOffset - nPrevLower;
4163                     }
4164                     // I find it not so good, but Writer3 feature:
4165                     // Check if distance by LineSpacing > Lower: this value is not
4166                     // stuck in the height of PrevPortion.
4167                     if ( !pPrev->IsInvalid() )
4168                     {
4169                         nExtraSpace = GetYValue( lcl_CalcExtraSpace( rPrevLSItem ) );
4170                         if ( nExtraSpace > nPrevLower )
4171                         {
4172                             sal_uInt16 nMoreLower = nExtraSpace - nPrevLower;
4173                             // Paragraph becomes 'bigger', 'grows' downwards:
4174                             if ( nMoreLower > pPortion->nFirstLineOffset )
4175                             {
4176                                 pPortion->nHeight += ( nMoreLower - pPortion->nFirstLineOffset );
4177                                 pPortion->nFirstLineOffset = nMoreLower;
4178                             }
4179                         }
4180                     }
4181                 }
4182             }
4183         }
4184     }
4185 }
4186 
4187 tools::Rectangle ImpEditEngine::GetEditCursor( ParaPortion* pPortion, sal_Int32 nIndex, GetCursorFlags nFlags )
4188 {
4189     OSL_ENSURE( pPortion->IsVisible(), "Why GetEditCursor() for an invisible paragraph?" );
4190     OSL_ENSURE( IsFormatted() || GetTextRanger(), "GetEditCursor: Not formatted" );
4191 
4192     /*
4193      GetCursorFlags::EndOfLine: If after the last character of a wrapped line, remaining
4194      at the end of the line, not the beginning of the next one.
4195      Purpose:   - END => really after the last character
4196                 - Selection....
4197     */
4198 
4199     long nY = pPortion->GetFirstLineOffset();
4200 
4201     const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
4202     sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
4203                         ? GetYValue( rLSItem.GetInterLineSpace() ) : 0;
4204 
4205     sal_Int32 nCurIndex = 0;
4206     sal_Int32 nLineCount = pPortion->GetLines().Count();
4207     OSL_ENSURE( nLineCount, "Empty ParaPortion in GetEditCursor!" );
4208     if (nLineCount == 0)
4209         return tools::Rectangle();
4210     const EditLine* pLine = nullptr;
4211     bool bEOL( nFlags & GetCursorFlags::EndOfLine );
4212     for (sal_Int32 nLine = 0; nLine < nLineCount; ++nLine)
4213     {
4214         const EditLine& rTmpLine = pPortion->GetLines()[nLine];
4215         if ( ( rTmpLine.GetStart() == nIndex ) || ( rTmpLine.IsIn( nIndex, bEOL ) ) )
4216         {
4217             pLine = &rTmpLine;
4218             break;
4219         }
4220 
4221         nCurIndex = nCurIndex + rTmpLine.GetLen();
4222         nY += rTmpLine.GetHeight();
4223         if ( !aStatus.IsOutliner() )
4224             nY += nSBL;
4225     }
4226     if ( !pLine )
4227     {
4228         // Cursor at the End of the paragraph.
4229         OSL_ENSURE( nIndex == nCurIndex, "Index dead wrong in GetEditCursor!" );
4230 
4231         pLine = &pPortion->GetLines()[nLineCount-1];
4232         nY -= pLine->GetHeight();
4233         if ( !aStatus.IsOutliner() )
4234             nY -= nSBL;
4235     }
4236 
4237     tools::Rectangle aEditCursor;
4238 
4239     aEditCursor.SetTop( nY );
4240     nY += pLine->GetHeight();
4241     aEditCursor.SetBottom( nY-1 );
4242 
4243     // Search within the line...
4244     long nX;
4245 
4246     if ( ( nIndex == pLine->GetStart() ) && ( nFlags & GetCursorFlags::StartOfLine ) )
4247     {
4248         Range aXRange = GetLineXPosStartEnd( pPortion, pLine );
4249         nX = !IsRightToLeft( GetEditDoc().GetPos( pPortion->GetNode() ) ) ? aXRange.Min() : aXRange.Max();
4250     }
4251     else if ( ( nIndex == pLine->GetEnd() ) && ( nFlags & GetCursorFlags::EndOfLine ) )
4252     {
4253         Range aXRange = GetLineXPosStartEnd( pPortion, pLine );
4254         nX = !IsRightToLeft( GetEditDoc().GetPos( pPortion->GetNode() ) ) ? aXRange.Max() : aXRange.Min();
4255     }
4256     else
4257     {
4258         nX = GetXPos( pPortion, pLine, nIndex, bool( nFlags & GetCursorFlags::PreferPortionStart ) );
4259     }
4260 
4261     aEditCursor.SetLeft(nX);
4262     aEditCursor.SetRight(nX);
4263 
4264     if ( nFlags & GetCursorFlags::TextOnly )
4265         aEditCursor.SetTop( aEditCursor.Bottom() - pLine->GetTxtHeight() + 1 );
4266     else
4267         aEditCursor.SetTop( aEditCursor.Bottom() - std::min( pLine->GetTxtHeight(), pLine->GetHeight() ) + 1 );
4268 
4269     return aEditCursor;
4270 }
4271 
4272 void ImpEditEngine::SetValidPaperSize( const Size& rNewSz )
4273 {
4274     aPaperSize = rNewSz;
4275 
4276     long nMinWidth = aStatus.AutoPageWidth() ? aMinAutoPaperSize.Width() : 0;
4277     long nMaxWidth = aStatus.AutoPageWidth() ? aMaxAutoPaperSize.Width() : 0x7FFFFFFF;
4278     long nMinHeight = aStatus.AutoPageHeight() ? aMinAutoPaperSize.Height() : 0;
4279     long nMaxHeight = aStatus.AutoPageHeight() ? aMaxAutoPaperSize.Height() : 0x7FFFFFFF;
4280 
4281     // Minimum/Maximum width:
4282     if ( aPaperSize.Width() < nMinWidth )
4283         aPaperSize.setWidth( nMinWidth );
4284     else if ( aPaperSize.Width() > nMaxWidth )
4285         aPaperSize.setWidth( nMaxWidth );
4286 
4287     // Minimum/Maximum height:
4288     if ( aPaperSize.Height() < nMinHeight )
4289         aPaperSize.setHeight( nMinHeight );
4290     else if ( aPaperSize.Height() > nMaxHeight )
4291         aPaperSize.setHeight( nMaxHeight );
4292 }
4293 
4294 std::shared_ptr<SvxForbiddenCharactersTable> const & ImpEditEngine::GetForbiddenCharsTable()
4295 {
4296     return EditDLL::Get().GetGlobalData()->GetForbiddenCharsTable();
4297 }
4298 
4299 void ImpEditEngine::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars)
4300 {
4301     EditDLL::Get().GetGlobalData()->SetForbiddenCharsTable( xForbiddenChars );
4302 }
4303 
4304 bool ImpEditEngine::IsVisualCursorTravelingEnabled()
4305 {
4306     bool bVisualCursorTravaling = false;
4307 
4308     if( !pCTLOptions )
4309         pCTLOptions.reset( new SvtCTLOptions );
4310 
4311     if ( pCTLOptions->IsCTLFontEnabled() && ( pCTLOptions->GetCTLCursorMovement() == SvtCTLOptions::MOVEMENT_VISUAL ) )
4312     {
4313         bVisualCursorTravaling = true;
4314     }
4315 
4316     return bVisualCursorTravaling;
4317 
4318 }
4319 
4320 bool ImpEditEngine::DoVisualCursorTraveling()
4321 {
4322     // Don't check if it's necessary, because we also need it when leaving the paragraph
4323     return IsVisualCursorTravelingEnabled();
4324 }
4325 
4326 IMPL_LINK_NOARG(ImpEditEngine, DocModified, LinkParamNone*, void)
4327 {
4328     aModifyHdl.Call( nullptr /*GetEditEnginePtr()*/ ); // NULL, because also used for Outliner
4329 }
4330 
4331 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
4332