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