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