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