xref: /core/vcl/source/edit/texteng.cxx (revision 73e3604f)
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 <tools/stream.hxx>
21 
22 #include <vcl/texteng.hxx>
23 #include <vcl/textview.hxx>
24 #include <vcl/commandevent.hxx>
25 #include <vcl/inputctx.hxx>
26 #include "textdoc.hxx"
27 #include "textdat2.hxx"
28 #include "textundo.hxx"
29 #include "textund2.hxx"
30 #include <svl/ctloptions.hxx>
31 #include <vcl/window.hxx>
32 #include <vcl/settings.hxx>
33 #include <vcl/edit.hxx>
34 #include <vcl/virdev.hxx>
35 #include <sal/log.hxx>
36 #include <osl/diagnose.h>
37 
38 #include <com/sun/star/i18n/XBreakIterator.hpp>
39 
40 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
41 
42 #include <com/sun/star/i18n/WordType.hpp>
43 
44 #include <com/sun/star/i18n/InputSequenceChecker.hpp>
45 #include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
46 #include <com/sun/star/i18n/ScriptType.hpp>
47 
48 #include <comphelper/processfactory.hxx>
49 
50 #include <unotools/localedatawrapper.hxx>
51 #include <vcl/unohelp.hxx>
52 
53 #include <vcl/svapp.hxx>
54 
55 #include <unicode/ubidi.h>
56 
57 #include <algorithm>
58 #include <cstddef>
59 #include <cstdlib>
60 #include <memory>
61 #include <set>
62 #include <string_view>
63 #include <vector>
64 
65 using namespace ::com::sun::star;
66 using namespace ::com::sun::star::uno;
67 
68 TextEngine::TextEngine()
69     : mpActiveView {nullptr}
70     , maTextColor {COL_BLACK}
71     , mnMaxTextLen {0}
72     , mnMaxTextWidth {0}
73     , mnCharHeight {0}
74     , mnCurTextWidth {-1}
75     , mnCurTextHeight {0}
76     , mnDefTab {0}
77     , meAlign {TxtAlign::Left}
78     , mbIsFormatting {false}
79     , mbFormatted {false}
80     , mbUpdate {true}
81     , mbModified {false}
82     , mbUndoEnabled {false}
83     , mbIsInUndo {false}
84     , mbDowning {false}
85     , mbRightToLeft {false}
86     , mbHasMultiLineParas {false}
87 {
88     mpViews.reset( new TextViews );
89 
90     mpIdleFormatter.reset( new IdleFormatter );
91     mpIdleFormatter->SetInvokeHandler( LINK( this, TextEngine, IdleFormatHdl ) );
92     mpIdleFormatter->SetDebugName( "vcl::TextEngine mpIdleFormatter" );
93 
94     mpRefDev = VclPtr<VirtualDevice>::Create();
95 
96     ImpInitLayoutMode( mpRefDev );
97 
98     ImpInitDoc();
99 
100     vcl::Font aFont;
101     aFont.SetTransparent( false );
102     Color aFillColor( aFont.GetFillColor() );
103     aFillColor.SetTransparency( 0 );
104     aFont.SetFillColor( aFillColor );
105     SetFont( aFont );
106 }
107 
108 TextEngine::~TextEngine()
109 {
110     mbDowning = true;
111 
112     mpIdleFormatter.reset();
113     mpDoc.reset();
114     mpTEParaPortions.reset();
115     mpViews.reset(); // only the list, not the Views
116     mpRefDev.disposeAndClear();
117     mpUndoManager.reset();
118     mpIMEInfos.reset();
119     mpLocaleDataWrapper.reset();
120 }
121 
122 void TextEngine::InsertView( TextView* pTextView )
123 {
124     mpViews->push_back( pTextView );
125     pTextView->SetSelection( TextSelection() );
126 
127     if ( !GetActiveView() )
128         SetActiveView( pTextView );
129 }
130 
131 void TextEngine::RemoveView( TextView* pTextView )
132 {
133     TextViews::iterator it = std::find( mpViews->begin(), mpViews->end(), pTextView );
134     if( it != mpViews->end() )
135     {
136         pTextView->HideCursor();
137         mpViews->erase( it );
138         if ( pTextView == GetActiveView() )
139             SetActiveView( nullptr );
140     }
141 }
142 
143 sal_uInt16 TextEngine::GetViewCount() const
144 {
145     return mpViews->size();
146 }
147 
148 TextView* TextEngine::GetView( sal_uInt16 nView ) const
149 {
150     return (*mpViews)[ nView ];
151 }
152 
153 
154 void TextEngine::SetActiveView( TextView* pTextView )
155 {
156     if ( pTextView != mpActiveView )
157     {
158         if ( mpActiveView )
159             mpActiveView->HideSelection();
160 
161         mpActiveView = pTextView;
162 
163         if ( mpActiveView )
164             mpActiveView->ShowSelection();
165     }
166 }
167 
168 void TextEngine::SetFont( const vcl::Font& rFont )
169 {
170     if (rFont == maOrigFont)
171         return;
172     maOrigFont = rFont;
173 
174     maFont = rFont;
175     // #i40221# As the font's color now defaults to transparent (since i35764)
176     //  we have to choose a useful textcolor in this case.
177     // Otherwise maTextColor and maFont.GetColor() are both transparent...
178     if( rFont.GetColor() == COL_TRANSPARENT )
179         maTextColor = COL_BLACK;
180     else
181         maTextColor = rFont.GetColor();
182 
183     // Do not allow transparent fonts because of selection
184     // (otherwise delete the background in ImplPaint later differently)
185     maFont.SetTransparent( false );
186     // Tell VCL not to use the font color, use text color from OutputDevice
187     maFont.SetColor( COL_TRANSPARENT );
188     Color aFillColor( maFont.GetFillColor() );
189     aFillColor.SetTransparency( 0 );
190     maFont.SetFillColor( aFillColor );
191 
192     maFont.SetAlignment( ALIGN_TOP );
193     mpRefDev->SetFont( maFont );
194     mnDefTab = mpRefDev->GetTextWidth("    ");
195     if ( !mnDefTab )
196         mnDefTab = mpRefDev->GetTextWidth("XXXX");
197     if ( !mnDefTab )
198         mnDefTab = 1;
199     mnCharHeight = mpRefDev->GetTextHeight();
200 
201     FormatFullDoc();
202     UpdateViews();
203 
204     for ( auto nView = mpViews->size(); nView; )
205     {
206         TextView* pView = (*mpViews)[ --nView ];
207         pView->GetWindow()->SetInputContext(InputContext(maFont, !pView->IsReadOnly()
208             ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE));
209     }
210 }
211 
212 void TextEngine::SetMaxTextLen( sal_Int32 nLen )
213 {
214     mnMaxTextLen = nLen>=0 ? nLen : EDIT_NOLIMIT;
215 }
216 
217 void TextEngine::SetMaxTextWidth( long nMaxWidth )
218 {
219     if ( nMaxWidth>=0 && nMaxWidth != mnMaxTextWidth )
220     {
221         mnMaxTextWidth = nMaxWidth;
222         FormatFullDoc();
223         UpdateViews();
224     }
225 }
226 
227 static const sal_Unicode static_aLFText[] = { '\n', 0 };
228 static const sal_Unicode static_aCRText[] = { '\r', 0 };
229 static const sal_Unicode static_aCRLFText[] = { '\r', '\n', 0 };
230 
231 static const sal_Unicode* static_getLineEndText( LineEnd aLineEnd )
232 {
233     const sal_Unicode* pRet = nullptr;
234 
235     switch( aLineEnd )
236     {
237         case LINEEND_LF:
238             pRet = static_aLFText;
239             break;
240         case LINEEND_CR:
241             pRet = static_aCRText;
242             break;
243         case LINEEND_CRLF:
244             pRet = static_aCRLFText;
245             break;
246     }
247     return pRet;
248 }
249 
250 void  TextEngine::ReplaceText(const TextSelection& rSel, const OUString& rText)
251 {
252     ImpInsertText( rSel, rText );
253 }
254 
255 OUString TextEngine::GetText( LineEnd aSeparator ) const
256 {
257     return mpDoc->GetText( static_getLineEndText( aSeparator ) );
258 }
259 
260 OUString TextEngine::GetTextLines( LineEnd aSeparator ) const
261 {
262     OUStringBuffer aText;
263     const sal_uInt32 nParas = mpTEParaPortions->Count();
264     const sal_Unicode* pSep = static_getLineEndText( aSeparator );
265     for ( sal_uInt32 nP = 0; nP < nParas; ++nP )
266     {
267         TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nP );
268 
269         const size_t nLines = pTEParaPortion->GetLines().size();
270         for ( size_t nL = 0; nL < nLines; ++nL )
271         {
272             TextLine& rLine = pTEParaPortion->GetLines()[nL];
273             aText.append( std::u16string_view(pTEParaPortion->GetNode()->GetText()).substr(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()) );
274             if ( pSep && ( ( (nP+1) < nParas ) || ( (nL+1) < nLines ) ) )
275                 aText.append(pSep);
276         }
277     }
278     return aText.makeStringAndClear();
279 }
280 
281 OUString TextEngine::GetText( sal_uInt32 nPara ) const
282 {
283     return mpDoc->GetText( nPara );
284 }
285 
286 sal_Int32 TextEngine::GetTextLen() const
287 {
288     return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ) );
289 }
290 
291 sal_Int32 TextEngine::GetTextLen( const TextSelection& rSel ) const
292 {
293     TextSelection aSel( rSel );
294     aSel.Justify();
295     ValidateSelection( aSel );
296     return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ), &aSel );
297 }
298 
299 sal_Int32 TextEngine::GetTextLen( const sal_uInt32 nPara ) const
300 {
301     return mpDoc->GetNodes()[ nPara ]->GetText().getLength();
302 }
303 
304 void TextEngine::SetUpdateMode( bool bUpdate )
305 {
306     if ( bUpdate != mbUpdate )
307     {
308         mbUpdate = bUpdate;
309         if ( mbUpdate )
310         {
311             FormatAndUpdate( GetActiveView() );
312             if ( GetActiveView() )
313                 GetActiveView()->ShowCursor();
314         }
315     }
316 }
317 
318 bool TextEngine::DoesKeyChangeText( const KeyEvent& rKeyEvent )
319 {
320     bool bDoesChange = false;
321 
322     KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
323     if ( eFunc != KeyFuncType::DONTKNOW )
324     {
325         switch ( eFunc )
326         {
327             case KeyFuncType::UNDO:
328             case KeyFuncType::REDO:
329             case KeyFuncType::CUT:
330             case KeyFuncType::PASTE:
331                 bDoesChange = true;
332                 break;
333             default:
334                 // might get handled below
335                 eFunc = KeyFuncType::DONTKNOW;
336         }
337     }
338     if ( eFunc == KeyFuncType::DONTKNOW )
339     {
340         switch ( rKeyEvent.GetKeyCode().GetCode() )
341         {
342             case KEY_DELETE:
343             case KEY_BACKSPACE:
344                 if ( !rKeyEvent.GetKeyCode().IsMod2() )
345                     bDoesChange = true;
346                 break;
347             case KEY_RETURN:
348             case KEY_TAB:
349                 if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() )
350                     bDoesChange = true;
351                 break;
352             default:
353                 bDoesChange = TextEngine::IsSimpleCharInput( rKeyEvent );
354         }
355     }
356     return bDoesChange;
357 }
358 
359 bool TextEngine::IsSimpleCharInput( const KeyEvent& rKeyEvent )
360 {
361     return rKeyEvent.GetCharCode() >= 32 && rKeyEvent.GetCharCode() != 127 &&
362         KEY_MOD1 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT) && // (ssa) #i45714#:
363         KEY_MOD2 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT);  // check for Ctrl and Alt separately
364 }
365 
366 void TextEngine::ImpInitDoc()
367 {
368     if ( mpDoc )
369         mpDoc->Clear();
370     else
371         mpDoc.reset( new TextDoc );
372 
373     mpTEParaPortions.reset(new TEParaPortions);
374 
375     std::unique_ptr<TextNode> pNode(new TextNode( OUString() ));
376     mpDoc->GetNodes().insert( mpDoc->GetNodes().begin(), std::move(pNode) );
377 
378     TEParaPortion* pIniPortion = new TEParaPortion( mpDoc->GetNodes().begin()->get() );
379     mpTEParaPortions->Insert( pIniPortion, 0 );
380 
381     mbFormatted = false;
382 
383     ImpParagraphRemoved( TEXT_PARA_ALL );
384     ImpParagraphInserted( 0 );
385 }
386 
387 OUString TextEngine::GetText( const TextSelection& rSel, LineEnd aSeparator ) const
388 {
389     if ( !rSel.HasRange() )
390         return OUString();
391 
392     TextSelection aSel( rSel );
393     aSel.Justify();
394 
395     OUStringBuffer aText;
396     const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
397     const sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
398     const sal_Unicode* pSep = static_getLineEndText( aSeparator );
399     for ( sal_uInt32 nNode = aSel.GetStart().GetPara(); nNode <= nEndPara; ++nNode )
400     {
401         TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
402 
403         sal_Int32 nStartPos = 0;
404         sal_Int32 nEndPos = pNode->GetText().getLength();
405         if ( nNode == nStartPara )
406             nStartPos = aSel.GetStart().GetIndex();
407         if ( nNode == nEndPara ) // may also be == nStart!
408             nEndPos = aSel.GetEnd().GetIndex();
409 
410         aText.append(std::u16string_view(pNode->GetText()).substr(nStartPos, nEndPos-nStartPos));
411         if ( nNode < nEndPara )
412             aText.append(pSep);
413     }
414     return aText.makeStringAndClear();
415 }
416 
417 void TextEngine::ImpRemoveText()
418 {
419     ImpInitDoc();
420 
421     const TextSelection aEmptySel;
422     for (TextView* pView : *mpViews)
423     {
424         pView->ImpSetSelection( aEmptySel );
425     }
426     ResetUndo();
427 }
428 
429 void TextEngine::SetText( const OUString& rText )
430 {
431     ImpRemoveText();
432 
433     const bool bUndoCurrentlyEnabled = IsUndoEnabled();
434     // the manually inserted text cannot be reversed by the user
435     EnableUndo( false );
436 
437     const TextSelection aEmptySel;
438 
439     TextPaM aPaM;
440     if ( !rText.isEmpty() )
441         aPaM = ImpInsertText( aEmptySel, rText );
442 
443     for (TextView* pView : *mpViews)
444     {
445         pView->ImpSetSelection( aEmptySel );
446 
447         // if no text, then no Format&Update => the text remains
448         if ( rText.isEmpty() && GetUpdateMode() )
449             pView->Invalidate();
450     }
451 
452     if( rText.isEmpty() )  // otherwise needs invalidation later; !bFormatted is sufficient
453         mnCurTextHeight = 0;
454 
455     FormatAndUpdate();
456 
457     EnableUndo( bUndoCurrentlyEnabled );
458     SAL_WARN_IF( HasUndoManager() && GetUndoManager().GetUndoActionCount(), "vcl", "SetText: Undo!" );
459 }
460 
461 void TextEngine::CursorMoved( sal_uInt32 nNode )
462 {
463     // delete empty attribute; but only if paragraph is not empty!
464     TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
465     if ( pNode && pNode->GetCharAttribs().HasEmptyAttribs() && !pNode->GetText().isEmpty() )
466         pNode->GetCharAttribs().DeleteEmptyAttribs();
467 }
468 
469 void TextEngine::ImpRemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
470 {
471     SAL_WARN_IF( !nChars, "vcl", "ImpRemoveChars: 0 Chars?!" );
472     if ( IsUndoEnabled() && !IsInUndo() )
473     {
474         // attributes have to be saved for UNDO before RemoveChars!
475         TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
476         OUString aStr( pNode->GetText().copy( rPaM.GetIndex(), nChars ) );
477 
478         // check if attributes are being deleted or changed
479         const sal_Int32 nStart = rPaM.GetIndex();
480         const sal_Int32 nEnd = nStart + nChars;
481         for ( sal_uInt16 nAttr = pNode->GetCharAttribs().Count(); nAttr; )
482         {
483             TextCharAttrib& rAttr = pNode->GetCharAttribs().GetAttrib( --nAttr );
484             if ( ( rAttr.GetEnd() >= nStart ) && ( rAttr.GetStart() < nEnd ) )
485             {
486                 break;  // for
487             }
488         }
489         InsertUndo( std::make_unique<TextUndoRemoveChars>( this, rPaM, aStr ) );
490     }
491 
492     mpDoc->RemoveChars( rPaM, nChars );
493     ImpCharsRemoved( rPaM.GetPara(), rPaM.GetIndex(), nChars );
494 }
495 
496 TextPaM TextEngine::ImpConnectParagraphs( sal_uInt32 nLeft, sal_uInt32 nRight )
497 {
498     SAL_WARN_IF( nLeft == nRight, "vcl", "ImpConnectParagraphs: connect the very same paragraph ?" );
499 
500     TextNode* pLeft = mpDoc->GetNodes()[ nLeft ].get();
501     TextNode* pRight = mpDoc->GetNodes()[ nRight ].get();
502 
503     if ( IsUndoEnabled() && !IsInUndo() )
504         InsertUndo( std::make_unique<TextUndoConnectParas>( this, nLeft, pLeft->GetText().getLength() ) );
505 
506     // first lookup Portions, as pRight is gone after ConnectParagraphs
507     TEParaPortion* pLeftPortion = mpTEParaPortions->GetObject( nLeft );
508     TEParaPortion* pRightPortion = mpTEParaPortions->GetObject( nRight );
509     SAL_WARN_IF( !pLeft || !pLeftPortion, "vcl", "ImpConnectParagraphs(1): Hidden Portion" );
510     SAL_WARN_IF( !pRight || !pRightPortion, "vcl", "ImpConnectParagraphs(2): Hidden Portion" );
511 
512     TextPaM aPaM = mpDoc->ConnectParagraphs( pLeft, pRight );
513     ImpParagraphRemoved( nRight );
514 
515     pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() );
516 
517     mpTEParaPortions->Remove( nRight );
518     // the right Node is deleted by EditDoc::ConnectParagraphs()
519 
520     return aPaM;
521 }
522 
523 TextPaM TextEngine::ImpDeleteText( const TextSelection& rSel )
524 {
525     if ( !rSel.HasRange() )
526         return rSel.GetStart();
527 
528     TextSelection aSel( rSel );
529     aSel.Justify();
530     TextPaM aStartPaM( aSel.GetStart() );
531     TextPaM aEndPaM( aSel.GetEnd() );
532 
533     CursorMoved( aStartPaM.GetPara() ); // so that newly-adjusted attributes vanish
534     CursorMoved( aEndPaM.GetPara() );   // so that newly-adjusted attributes vanish
535 
536     SAL_WARN_IF( !mpDoc->IsValidPaM( aStartPaM ), "vcl", "ImpDeleteText(1): bad Index" );
537     SAL_WARN_IF( !mpDoc->IsValidPaM( aEndPaM ), "vcl", "ImpDeleteText(2): bad Index" );
538 
539     const sal_uInt32 nStartNode = aStartPaM.GetPara();
540     sal_uInt32 nEndNode = aEndPaM.GetPara();
541 
542     // remove all Nodes inbetween
543     for ( sal_uInt32 z = nStartNode+1; z < nEndNode; ++z )
544     {
545         // always nStartNode+1, because of Remove()!
546         ImpRemoveParagraph( nStartNode+1 );
547     }
548 
549     if ( nStartNode != nEndNode )
550     {
551         // the remainder of StartNodes...
552         TextNode* pLeft = mpDoc->GetNodes()[ nStartNode ].get();
553         sal_Int32 nChars = pLeft->GetText().getLength() - aStartPaM.GetIndex();
554         if ( nChars )
555         {
556             ImpRemoveChars( aStartPaM, nChars );
557             TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
558             SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(3): bad Index" );
559             pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() );
560         }
561 
562         // the beginning of EndNodes...
563         nEndNode = nStartNode+1;    // the other paragraphs were deleted
564         nChars = aEndPaM.GetIndex();
565         if ( nChars )
566         {
567             aEndPaM.GetPara() = nEndNode;
568             aEndPaM.GetIndex() = 0;
569             ImpRemoveChars( aEndPaM, nChars );
570             TEParaPortion* pPortion = mpTEParaPortions->GetObject( nEndNode );
571             SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(4): bad Index" );
572             pPortion->MarkSelectionInvalid( 0 );
573         }
574 
575         // connect...
576         aStartPaM = ImpConnectParagraphs( nStartNode, nEndNode );
577     }
578     else
579     {
580         const sal_Int32 nChars = aEndPaM.GetIndex() - aStartPaM.GetIndex();
581         ImpRemoveChars( aStartPaM, nChars );
582         TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
583         SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(5): bad Index" );
584         pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() );
585     }
586 
587 //  UpdateSelections();
588     TextModified();
589     return aStartPaM;
590 }
591 
592 void TextEngine::ImpRemoveParagraph( sal_uInt32 nPara )
593 {
594     std::unique_ptr<TextNode> pNode = std::move(mpDoc->GetNodes()[ nPara ]);
595 
596     // the Node is handled by Undo and is deleted if appropriate
597     mpDoc->GetNodes().erase( mpDoc->GetNodes().begin() + nPara );
598     if ( IsUndoEnabled() && !IsInUndo() )
599         InsertUndo( std::make_unique<TextUndoDelPara>( this, pNode.release(), nPara ) );
600 
601     mpTEParaPortions->Remove( nPara );
602 
603     ImpParagraphRemoved( nPara );
604 }
605 
606 uno::Reference < i18n::XExtendedInputSequenceChecker > const & TextEngine::GetInputSequenceChecker()
607 {
608     if ( !mxISC.is() )
609     {
610         mxISC = i18n::InputSequenceChecker::create( ::comphelper::getProcessComponentContext() );
611     }
612     return mxISC;
613 }
614 
615 bool TextEngine::IsInputSequenceCheckingRequired( sal_Unicode c, const TextSelection& rCurSel ) const
616 {
617     SvtCTLOptions aCTLOptions;
618 
619     // get the index that really is first
620     const sal_Int32 nFirstPos = std::min(rCurSel.GetStart().GetIndex(), rCurSel.GetEnd().GetIndex());
621 
622     bool bIsSequenceChecking =
623         aCTLOptions.IsCTLFontEnabled() &&
624         aCTLOptions.IsCTLSequenceChecking() &&
625         nFirstPos != 0; /* first char needs not to be checked */
626 
627     if (bIsSequenceChecking)
628     {
629         uno::Reference< i18n::XBreakIterator > xBI = const_cast<TextEngine *>(this)->GetBreakIterator();
630         bIsSequenceChecking = xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( OUString( c ), 0 );
631     }
632 
633     return bIsSequenceChecking;
634 }
635 
636 TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, sal_Unicode c, bool bOverwrite )
637 {
638     return ImpInsertText( c, rCurSel, bOverwrite  );
639 }
640 
641 TextPaM TextEngine::ImpInsertText( sal_Unicode c, const TextSelection& rCurSel, bool bOverwrite, bool bIsUserInput )
642 {
643     SAL_WARN_IF( c == '\n', "vcl", "InsertText: NewLine!" );
644     SAL_WARN_IF( c == '\r', "vcl", "InsertText: NewLine!" );
645 
646     TextPaM aPaM( rCurSel.GetStart() );
647     TextNode* pNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
648 
649     bool bDoOverwrite = bOverwrite && ( aPaM.GetIndex() < pNode->GetText().getLength() );
650 
651     bool bUndoAction = rCurSel.HasRange() || bDoOverwrite;
652 
653     if ( bUndoAction )
654         UndoActionStart();
655 
656     if ( rCurSel.HasRange() )
657     {
658         aPaM = ImpDeleteText( rCurSel );
659     }
660     else if ( bDoOverwrite )
661     {
662         // if selection, then don't overwrite a character
663         TextSelection aTmpSel( aPaM );
664         ++aTmpSel.GetEnd().GetIndex();
665         ImpDeleteText( aTmpSel );
666     }
667 
668     if (bIsUserInput && IsInputSequenceCheckingRequired( c, rCurSel ))
669     {
670         uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = GetInputSequenceChecker();
671         SvtCTLOptions aCTLOptions;
672 
673         if (xISC.is())
674         {
675             sal_Int32 nTmpPos = aPaM.GetIndex();
676             sal_Int16 nCheckMode = aCTLOptions.IsCTLSequenceCheckingRestricted() ?
677                     i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
678 
679             // the text that needs to be checked is only the one
680             // before the current cursor position
681             OUString aOldText( mpDoc->GetText( aPaM.GetPara() ).copy(0, nTmpPos) );
682             if (aCTLOptions.IsCTLSequenceCheckingTypeAndReplace())
683             {
684                 OUString aNewText( aOldText );
685                 xISC->correctInputSequence( aNewText, nTmpPos - 1, c, nCheckMode );
686 
687                 // find position of first character that has changed
688                 const sal_Int32 nOldLen = aOldText.getLength();
689                 const sal_Int32 nNewLen = aNewText.getLength();
690                 const sal_Unicode *pOldTxt = aOldText.getStr();
691                 const sal_Unicode *pNewTxt = aNewText.getStr();
692                 sal_Int32 nChgPos = 0;
693                 while ( nChgPos < nOldLen && nChgPos < nNewLen &&
694                         pOldTxt[nChgPos] == pNewTxt[nChgPos] )
695                     ++nChgPos;
696 
697                 OUString aChgText( aNewText.copy( nChgPos ) );
698 
699                 // select text from first pos to be changed to current pos
700                 TextSelection aSel( TextPaM( aPaM.GetPara(), nChgPos ), aPaM );
701 
702                 if (!aChgText.isEmpty())
703                     // ImpInsertText implicitly handles undo...
704                     return ImpInsertText( aSel, aChgText );
705                 else
706                     return aPaM;
707             }
708             else
709             {
710                 // should the character be ignored (i.e. not get inserted) ?
711                 if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode ))
712                     return aPaM;    // nothing to be done -> no need for undo
713             }
714         }
715 
716         // at this point now we will insert the character 'normally' some lines below...
717     }
718 
719     if ( IsUndoEnabled() && !IsInUndo() )
720     {
721         std::unique_ptr<TextUndoInsertChars> pNewUndo(new TextUndoInsertChars( this, aPaM, OUString(c) ));
722         bool bTryMerge = !bDoOverwrite && ( c != ' ' );
723         InsertUndo( std::move(pNewUndo), bTryMerge );
724     }
725 
726     TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
727     pPortion->MarkInvalid( aPaM.GetIndex(), 1 );
728     if ( c == '\t' )
729         pPortion->SetNotSimpleInvalid();
730     aPaM = mpDoc->InsertText( aPaM, c );
731     ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-1, 1 );
732 
733     TextModified();
734 
735     if ( bUndoAction )
736         UndoActionEnd();
737 
738     return aPaM;
739 }
740 
741 TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, const OUString& rStr )
742 {
743     UndoActionStart();
744 
745     TextPaM aPaM;
746 
747     if ( rCurSel.HasRange() )
748         aPaM = ImpDeleteText( rCurSel );
749     else
750         aPaM = rCurSel.GetEnd();
751 
752     OUString aText(convertLineEnd(rStr, LINEEND_LF));
753 
754     sal_Int32 nStart = 0;
755     while ( nStart < aText.getLength() )
756     {
757         sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart );
758         if (nEnd == -1)
759             nEnd = aText.getLength(); // do not dereference!
760 
761         // Start == End => empty line
762         if ( nEnd > nStart )
763         {
764             OUString aLine(aText.copy(nStart, nEnd-nStart));
765             if ( IsUndoEnabled() && !IsInUndo() )
766                 InsertUndo( std::make_unique<TextUndoInsertChars>( this, aPaM, aLine ) );
767 
768             TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
769             pPortion->MarkInvalid( aPaM.GetIndex(), aLine.getLength() );
770             if (aLine.indexOf( '\t' ) != -1)
771                 pPortion->SetNotSimpleInvalid();
772 
773             aPaM = mpDoc->InsertText( aPaM, aLine );
774             ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-aLine.getLength(), aLine.getLength() );
775 
776         }
777         if ( nEnd < aText.getLength() )
778             aPaM = ImpInsertParaBreak( aPaM );
779 
780         if ( nEnd == aText.getLength() )    // #108611# prevent overflow in "nStart = nEnd+1" calculation
781             break;
782 
783         nStart = nEnd+1;
784     }
785 
786     UndoActionEnd();
787 
788     TextModified();
789     return aPaM;
790 }
791 
792 TextPaM TextEngine::ImpInsertParaBreak( const TextSelection& rCurSel )
793 {
794     TextPaM aPaM;
795     if ( rCurSel.HasRange() )
796         aPaM = ImpDeleteText( rCurSel );
797     else
798         aPaM = rCurSel.GetEnd();
799 
800     return ImpInsertParaBreak( aPaM );
801 }
802 
803 TextPaM TextEngine::ImpInsertParaBreak( const TextPaM& rPaM )
804 {
805     if ( IsUndoEnabled() && !IsInUndo() )
806         InsertUndo( std::make_unique<TextUndoSplitPara>( this, rPaM.GetPara(), rPaM.GetIndex() ) );
807 
808     TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
809     bool bFirstParaContentChanged = rPaM.GetIndex() < pNode->GetText().getLength();
810 
811     TextPaM aPaM( mpDoc->InsertParaBreak( rPaM ) );
812 
813     TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
814     SAL_WARN_IF( !pPortion, "vcl", "ImpInsertParaBreak: Hidden Portion" );
815     pPortion->MarkInvalid( rPaM.GetIndex(), 0 );
816 
817     TextNode* pNewNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
818     TEParaPortion* pNewPortion = new TEParaPortion( pNewNode );
819     mpTEParaPortions->Insert( pNewPortion, aPaM.GetPara() );
820     ImpParagraphInserted( aPaM.GetPara() );
821 
822     CursorMoved( rPaM.GetPara() );  // if empty attribute created
823     TextModified();
824 
825     if ( bFirstParaContentChanged )
826         Broadcast( TextHint( SfxHintId::TextParaContentChanged, rPaM.GetPara() ) );
827 
828     return aPaM;
829 }
830 
831 tools::Rectangle TextEngine::PaMtoEditCursor( const TextPaM& rPaM, bool bSpecial )
832 {
833     SAL_WARN_IF( !GetUpdateMode(), "vcl", "PaMtoEditCursor: GetUpdateMode()" );
834 
835     tools::Rectangle aEditCursor;
836     long nY = 0;
837 
838     if ( !mbHasMultiLineParas )
839     {
840         nY = rPaM.GetPara() * mnCharHeight;
841     }
842     else
843     {
844         for ( sal_uInt32 nPortion = 0; nPortion < rPaM.GetPara(); ++nPortion )
845         {
846             TEParaPortion* pPortion = mpTEParaPortions->GetObject(nPortion);
847             nY += pPortion->GetLines().size() * mnCharHeight;
848         }
849     }
850 
851     aEditCursor = GetEditCursor( rPaM, bSpecial );
852     aEditCursor.AdjustTop(nY );
853     aEditCursor.AdjustBottom(nY );
854     return aEditCursor;
855 }
856 
857 tools::Rectangle TextEngine::GetEditCursor( const TextPaM& rPaM, bool bSpecial, bool bPreferPortionStart )
858 {
859     if ( !IsFormatted() && !IsFormatting() )
860         FormatAndUpdate();
861 
862     TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
863     //TextNode* pNode = mpDoc->GetNodes().GetObject( rPaM.GetPara() );
864 
865     /*
866       bSpecial: If behind the last character of a made up line, stay at the
867                   end of the line, not at the start of the next line.
868       Purpose:  - really END = > behind the last character
869                 - to selection...
870 
871     */
872 
873     long nY = 0;
874     sal_Int32 nCurIndex = 0;
875     TextLine* pLine = nullptr;
876     for (TextLine & rTmpLine : pPortion->GetLines())
877     {
878         if ( ( rTmpLine.GetStart() == rPaM.GetIndex() ) || ( rTmpLine.IsIn( rPaM.GetIndex(), bSpecial ) ) )
879         {
880             pLine = &rTmpLine;
881             break;
882         }
883 
884         nCurIndex = nCurIndex + rTmpLine.GetLen();
885         nY += mnCharHeight;
886     }
887     if ( !pLine )
888     {
889         // Cursor at end of paragraph
890         SAL_WARN_IF( rPaM.GetIndex() != nCurIndex, "vcl", "GetEditCursor: Bad Index!" );
891 
892         pLine = & ( pPortion->GetLines().back() );
893         nY -= mnCharHeight;
894     }
895 
896     tools::Rectangle aEditCursor;
897 
898     aEditCursor.SetTop( nY );
899     nY += mnCharHeight;
900     aEditCursor.SetBottom( nY-1 );
901 
902     // search within the line
903     long nX = ImpGetXPos( rPaM.GetPara(), pLine, rPaM.GetIndex(), bPreferPortionStart );
904     aEditCursor.SetLeft(nX);
905     aEditCursor.SetRight(nX);
906     return aEditCursor;
907 }
908 
909 long TextEngine::ImpGetXPos( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart )
910 {
911     SAL_WARN_IF( ( nIndex < pLine->GetStart() ) || ( nIndex > pLine->GetEnd() ) , "vcl", "ImpGetXPos: Bad parameters!" );
912 
913     bool bDoPreferPortionStart = bPreferPortionStart;
914     // Assure that the portion belongs to this line
915     if ( nIndex == pLine->GetStart() )
916         bDoPreferPortionStart = true;
917     else if ( nIndex == pLine->GetEnd() )
918         bDoPreferPortionStart = false;
919 
920     TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
921 
922     sal_Int32 nTextPortionStart = 0;
923     std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart );
924 
925     SAL_WARN_IF( ( nTextPortion < pLine->GetStartPortion() ) || ( nTextPortion > pLine->GetEndPortion() ), "vcl", "GetXPos: Portion not in current line!" );
926 
927     TETextPortion* pPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
928 
929     long nX = ImpGetPortionXOffset( nPara, pLine, nTextPortion );
930 
931     long nPortionTextWidth = pPortion->GetWidth();
932 
933     if ( nTextPortionStart != nIndex )
934     {
935         // Search within portion...
936         if ( nIndex == ( nTextPortionStart + pPortion->GetLen() ) )
937         {
938             // End of Portion
939             if ( ( pPortion->GetKind() == PORTIONKIND_TAB ) ||
940                  ( !IsRightToLeft() && !pPortion->IsRightToLeft() ) ||
941                  ( IsRightToLeft() && pPortion->IsRightToLeft() ) )
942             {
943                 nX += nPortionTextWidth;
944                 if ( ( pPortion->GetKind() == PORTIONKIND_TAB ) && ( (nTextPortion+1) < pParaPortion->GetTextPortions().size() ) )
945                 {
946                     TETextPortion* pNextPortion = pParaPortion->GetTextPortions()[ nTextPortion+1 ];
947                     if (pNextPortion->GetKind() != PORTIONKIND_TAB && IsRightToLeft() != pNextPortion->IsRightToLeft())
948                     {
949                         // End of the tab portion, use start of next for cursor pos
950                         SAL_WARN_IF( bPreferPortionStart, "vcl", "ImpGetXPos: How can we get here!" );
951                         nX = ImpGetXPos( nPara, pLine, nIndex, true );
952                     }
953 
954                 }
955             }
956         }
957         else if ( pPortion->GetKind() == PORTIONKIND_TEXT )
958         {
959             SAL_WARN_IF( nIndex == pLine->GetStart(), "vcl", "ImpGetXPos: Strange behavior" );
960 
961             long nPosInPortion = CalcTextWidth( nPara, nTextPortionStart, nIndex-nTextPortionStart );
962 
963             if (IsRightToLeft() == pPortion->IsRightToLeft())
964             {
965                 nX += nPosInPortion;
966             }
967             else
968             {
969                 nX += nPortionTextWidth - nPosInPortion;
970             }
971         }
972     }
973     else // if ( nIndex == pLine->GetStart() )
974     {
975         if (pPortion->GetKind() != PORTIONKIND_TAB && IsRightToLeft() != pPortion->IsRightToLeft())
976         {
977             nX += nPortionTextWidth;
978         }
979     }
980 
981     return nX;
982 }
983 
984 const TextAttrib* TextEngine::FindAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
985 {
986     const TextAttrib* pAttr = nullptr;
987     const TextCharAttrib* pCharAttr = FindCharAttrib( rPaM, nWhich );
988     if ( pCharAttr )
989         pAttr = &pCharAttr->GetAttr();
990     return pAttr;
991 }
992 
993 const TextCharAttrib* TextEngine::FindCharAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
994 {
995     const TextCharAttrib* pAttr = nullptr;
996     TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
997     if (pNode && (rPaM.GetIndex() <= pNode->GetText().getLength()))
998         pAttr = pNode->GetCharAttribs().FindAttrib( nWhich, rPaM.GetIndex() );
999     return pAttr;
1000 }
1001 
1002 TextPaM TextEngine::GetPaM( const Point& rDocPos )
1003 {
1004     SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetPaM: GetUpdateMode()" );
1005 
1006     long nY = 0;
1007     for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
1008     {
1009         TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
1010         long nTmpHeight = pPortion->GetLines().size() * mnCharHeight;
1011         nY += nTmpHeight;
1012         if ( nY > rDocPos.Y() )
1013         {
1014             nY -= nTmpHeight;
1015             Point aPosInPara( rDocPos );
1016             aPosInPara.AdjustY( -nY );
1017 
1018             TextPaM aPaM( nPortion, 0 );
1019             aPaM.GetIndex() = ImpFindIndex( nPortion, aPosInPara );
1020             return aPaM;
1021         }
1022     }
1023 
1024     // not found - go to last visible
1025     const sal_uInt32 nLastNode = static_cast<sal_uInt32>(mpDoc->GetNodes().size() - 1);
1026     TextNode* pLast = mpDoc->GetNodes()[ nLastNode ].get();
1027     return TextPaM( nLastNode, pLast->GetText().getLength() );
1028 }
1029 
1030 sal_Int32 TextEngine::ImpFindIndex( sal_uInt32 nPortion, const Point& rPosInPara )
1031 {
1032     SAL_WARN_IF( !IsFormatted(), "vcl", "GetPaM: Not formatted" );
1033     TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
1034 
1035     sal_Int32 nCurIndex = 0;
1036 
1037     long nY = 0;
1038     TextLine* pLine = nullptr;
1039     std::vector<TextLine>::size_type nLine;
1040     for ( nLine = 0; nLine < pPortion->GetLines().size(); nLine++ )
1041     {
1042         TextLine& rmpLine = pPortion->GetLines()[ nLine ];
1043         nY += mnCharHeight;
1044         if ( nY > rPosInPara.Y() )  // that's it
1045         {
1046             pLine = &rmpLine;
1047             break;                  // correct Y-Position not needed
1048         }
1049     }
1050 
1051     assert(pLine && "ImpFindIndex: pLine ?");
1052 
1053     nCurIndex = GetCharPos( nPortion, nLine, rPosInPara.X() );
1054 
1055     if ( nCurIndex && ( nCurIndex == pLine->GetEnd() ) &&
1056          ( pLine != &( pPortion->GetLines().back() ) ) )
1057     {
1058         uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
1059         sal_Int32 nCount = 1;
1060         nCurIndex = xBI->previousCharacters( pPortion->GetNode()->GetText(), nCurIndex, GetLocale(), i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
1061     }
1062     return nCurIndex;
1063 }
1064 
1065 sal_Int32 TextEngine::GetCharPos( sal_uInt32 nPortion, std::vector<TextLine>::size_type nLine, long nXPos )
1066 {
1067 
1068     TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
1069     TextLine& rLine = pPortion->GetLines()[ nLine ];
1070 
1071     sal_Int32 nCurIndex = rLine.GetStart();
1072 
1073     long nTmpX = rLine.GetStartX();
1074     if ( nXPos <= nTmpX )
1075         return nCurIndex;
1076 
1077     for ( std::size_t i = rLine.GetStartPortion(); i <= rLine.GetEndPortion(); i++ )
1078     {
1079         TETextPortion* pTextPortion = pPortion->GetTextPortions()[ i ];
1080         nTmpX += pTextPortion->GetWidth();
1081 
1082         if ( nTmpX > nXPos )
1083         {
1084             if( pTextPortion->GetLen() > 1 )
1085             {
1086                 nTmpX -= pTextPortion->GetWidth();  // position before Portion
1087                 // TODO: Optimize: no GetTextBreak if fixed-width Font
1088                 vcl::Font aFont;
1089                 SeekCursor( nPortion, nCurIndex+1, aFont, nullptr );
1090                 mpRefDev->SetFont( aFont);
1091                 long nPosInPortion = nXPos-nTmpX;
1092                 if ( IsRightToLeft() != pTextPortion->IsRightToLeft() )
1093                     nPosInPortion = pTextPortion->GetWidth() - nPosInPortion;
1094                 nCurIndex = mpRefDev->GetTextBreak( pPortion->GetNode()->GetText(), nPosInPortion, nCurIndex );
1095                 // MT: GetTextBreak should assure that we are not within a CTL cell...
1096             }
1097             return nCurIndex;
1098         }
1099         nCurIndex += pTextPortion->GetLen();
1100     }
1101     return nCurIndex;
1102 }
1103 
1104 long TextEngine::GetTextHeight() const
1105 {
1106     SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
1107 
1108     if ( !IsFormatted() && !IsFormatting() )
1109         const_cast<TextEngine*>(this)->FormatAndUpdate();
1110 
1111     return mnCurTextHeight;
1112 }
1113 
1114 long TextEngine::GetTextHeight( sal_uInt32 nParagraph ) const
1115 {
1116     SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
1117 
1118     if ( !IsFormatted() && !IsFormatting() )
1119         const_cast<TextEngine*>(this)->FormatAndUpdate();
1120 
1121     return CalcParaHeight( nParagraph );
1122 }
1123 
1124 long TextEngine::CalcTextWidth( sal_uInt32 nPara )
1125 {
1126     long nParaWidth = 0;
1127     TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
1128     for ( auto nLine = pPortion->GetLines().size(); nLine; )
1129     {
1130         long nLineWidth = 0;
1131         TextLine& rLine = pPortion->GetLines()[ --nLine ];
1132         for ( std::size_t nTP = rLine.GetStartPortion(); nTP <= rLine.GetEndPortion(); nTP++ )
1133         {
1134             TETextPortion* pTextPortion = pPortion->GetTextPortions()[ nTP ];
1135             nLineWidth += pTextPortion->GetWidth();
1136         }
1137         if ( nLineWidth > nParaWidth )
1138             nParaWidth = nLineWidth;
1139     }
1140     return nParaWidth;
1141 }
1142 
1143 long TextEngine::CalcTextWidth()
1144 {
1145     if ( !IsFormatted() && !IsFormatting() )
1146         FormatAndUpdate();
1147 
1148     if ( mnCurTextWidth < 0 )
1149     {
1150         mnCurTextWidth = 0;
1151         for ( sal_uInt32 nPara = mpTEParaPortions->Count(); nPara; )
1152         {
1153             const long nParaWidth = CalcTextWidth( --nPara );
1154             if ( nParaWidth > mnCurTextWidth )
1155                 mnCurTextWidth = nParaWidth;
1156         }
1157     }
1158     return mnCurTextWidth+1;// wider by 1, as CreateLines breaks at >=
1159 }
1160 
1161 long TextEngine::CalcTextHeight()
1162 {
1163     SAL_WARN_IF( !GetUpdateMode(), "vcl", "CalcTextHeight: GetUpdateMode()" );
1164 
1165     long nY = 0;
1166     for ( auto nPortion = mpTEParaPortions->Count(); nPortion; )
1167         nY += CalcParaHeight( --nPortion );
1168     return nY;
1169 }
1170 
1171 long TextEngine::CalcTextWidth( sal_uInt32 nPara, sal_Int32 nPortionStart, sal_Int32 nLen )
1172 {
1173 #ifdef DBG_UTIL
1174     // within the text there must not be a Portion change (attribute/tab)!
1175     sal_Int32 nTabPos = mpDoc->GetNodes()[ nPara ]->GetText().indexOf( '\t', nPortionStart );
1176     SAL_WARN_IF( nTabPos != -1 && nTabPos < (nPortionStart+nLen), "vcl", "CalcTextWidth: Tab!" );
1177 #endif
1178 
1179     vcl::Font aFont;
1180     SeekCursor( nPara, nPortionStart+1, aFont, nullptr );
1181     mpRefDev->SetFont( aFont );
1182     TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1183     long nWidth = mpRefDev->GetTextWidth( pNode->GetText(), nPortionStart, nLen );
1184     return nWidth;
1185 }
1186 
1187 void TextEngine::GetTextPortionRange(const TextPaM& rPaM, sal_Int32& nStart, sal_Int32& nEnd)
1188 {
1189     nStart = 0;
1190     nEnd = 0;
1191     TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
1192     for ( std::size_t i = 0; i < pParaPortion->GetTextPortions().size(); ++i )
1193     {
1194         TETextPortion* pTextPortion = pParaPortion->GetTextPortions()[ i ];
1195         if (nStart + pTextPortion->GetLen() > rPaM.GetIndex())
1196         {
1197             nEnd = nStart + pTextPortion->GetLen();
1198             return;
1199         }
1200         else
1201         {
1202             nStart += pTextPortion->GetLen();
1203         }
1204     }
1205 }
1206 
1207 sal_uInt16 TextEngine::GetLineCount( sal_uInt32 nParagraph ) const
1208 {
1209     SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
1210 
1211     TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
1212     if ( pPPortion )
1213         return pPPortion->GetLines().size();
1214 
1215     return 0;
1216 }
1217 
1218 sal_Int32 TextEngine::GetLineLen( sal_uInt32 nParagraph, sal_uInt16 nLine ) const
1219 {
1220     SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
1221 
1222     TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
1223     if ( pPPortion && ( nLine < pPPortion->GetLines().size() ) )
1224     {
1225         return pPPortion->GetLines()[ nLine ].GetLen();
1226     }
1227 
1228     return 0;
1229 }
1230 
1231 long TextEngine::CalcParaHeight( sal_uInt32 nParagraph ) const
1232 {
1233     long nHeight = 0;
1234 
1235     TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
1236     SAL_WARN_IF( !pPPortion, "vcl", "GetParaHeight: paragraph not found" );
1237     if ( pPPortion )
1238         nHeight = pPPortion->GetLines().size() * mnCharHeight;
1239 
1240     return nHeight;
1241 }
1242 
1243 Range TextEngine::GetInvalidYOffsets( sal_uInt32 nPortion )
1244 {
1245     TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
1246     sal_uInt16 nLines = pTEParaPortion->GetLines().size();
1247     sal_uInt16 nLastInvalid, nFirstInvalid = 0;
1248     sal_uInt16 nLine;
1249     for ( nLine = 0; nLine < nLines; nLine++ )
1250     {
1251         TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
1252         if ( rL.IsInvalid() )
1253         {
1254             nFirstInvalid = nLine;
1255             break;
1256         }
1257     }
1258 
1259     for ( nLastInvalid = nFirstInvalid; nLastInvalid < nLines; nLastInvalid++ )
1260     {
1261         TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
1262         if ( rL.IsValid() )
1263             break;
1264     }
1265 
1266     if ( nLastInvalid >= nLines )
1267         nLastInvalid = nLines-1;
1268 
1269     return Range( nFirstInvalid*mnCharHeight, ((nLastInvalid+1)*mnCharHeight)-1 );
1270 }
1271 
1272 sal_uInt32 TextEngine::GetParagraphCount() const
1273 {
1274     return static_cast<sal_uInt32>(mpDoc->GetNodes().size());
1275 }
1276 
1277 void TextEngine::EnableUndo( bool bEnable )
1278 {
1279     // delete list when switching mode
1280     if ( bEnable != IsUndoEnabled() )
1281         ResetUndo();
1282 
1283     mbUndoEnabled = bEnable;
1284 }
1285 
1286 SfxUndoManager& TextEngine::GetUndoManager()
1287 {
1288     if ( !mpUndoManager )
1289         mpUndoManager.reset( new TextUndoManager( this ) );
1290     return *mpUndoManager;
1291 }
1292 
1293 void TextEngine::UndoActionStart( sal_uInt16 nId )
1294 {
1295     if ( IsUndoEnabled() && !IsInUndo() )
1296     {
1297         GetUndoManager().EnterListAction( OUString(), OUString(), nId, ViewShellId(-1) );
1298     }
1299 }
1300 
1301 void TextEngine::UndoActionEnd()
1302 {
1303     if ( IsUndoEnabled() && !IsInUndo() )
1304         GetUndoManager().LeaveListAction();
1305 }
1306 
1307 void TextEngine::InsertUndo( std::unique_ptr<TextUndo> pUndo, bool bTryMerge )
1308 {
1309     SAL_WARN_IF( IsInUndo(), "vcl", "InsertUndo: in Undo mode!" );
1310     GetUndoManager().AddUndoAction( std::move(pUndo), bTryMerge );
1311 }
1312 
1313 void TextEngine::ResetUndo()
1314 {
1315     if ( mpUndoManager )
1316         mpUndoManager->Clear();
1317 }
1318 
1319 void TextEngine::InsertContent( std::unique_ptr<TextNode> pNode, sal_uInt32 nPara )
1320 {
1321     SAL_WARN_IF( !pNode, "vcl", "InsertContent: NULL-Pointer!" );
1322     SAL_WARN_IF( !IsInUndo(), "vcl", "InsertContent: only in Undo()!" );
1323     TEParaPortion* pNew = new TEParaPortion( pNode.get() );
1324     mpTEParaPortions->Insert( pNew, nPara );
1325     mpDoc->GetNodes().insert( mpDoc->GetNodes().begin() + nPara, std::move(pNode) );
1326     ImpParagraphInserted( nPara );
1327 }
1328 
1329 TextPaM TextEngine::SplitContent( sal_uInt32 nNode, sal_Int32 nSepPos )
1330 {
1331 #ifdef DBG_UTIL
1332     TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
1333     SAL_WARN_IF( !pNode, "vcl", "SplitContent: Invalid Node!" );
1334     SAL_WARN_IF( !IsInUndo(), "vcl", "SplitContent: only in Undo()!" );
1335     SAL_WARN_IF( nSepPos > pNode->GetText().getLength(), "vcl", "SplitContent: Bad index" );
1336 #endif
1337     TextPaM aPaM( nNode, nSepPos );
1338     return ImpInsertParaBreak( aPaM );
1339 }
1340 
1341 TextPaM TextEngine::ConnectContents( sal_uInt32 nLeftNode )
1342 {
1343     SAL_WARN_IF( !IsInUndo(), "vcl", "ConnectContent: only in Undo()!" );
1344     return ImpConnectParagraphs( nLeftNode, nLeftNode+1 );
1345 }
1346 
1347 void TextEngine::SeekCursor( sal_uInt32 nPara, sal_Int32 nPos, vcl::Font& rFont, OutputDevice* pOutDev )
1348 {
1349     rFont = maFont;
1350     if ( pOutDev )
1351         pOutDev->SetTextColor( maTextColor );
1352 
1353     TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1354     sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
1355     for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
1356     {
1357         TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
1358         if ( rAttrib.GetStart() > nPos )
1359             break;
1360 
1361         // When seeking don't use Attr that start there!
1362         // Do not use empty attributes:
1363         // - If just being setup and empty => no effect on Font
1364         // - Characters that are setup in an empty paragraph become visible right away.
1365         if ( ( ( rAttrib.GetStart() < nPos ) && ( rAttrib.GetEnd() >= nPos ) )
1366                     || pNode->GetText().isEmpty() )
1367         {
1368             if ( rAttrib.Which() != TEXTATTR_FONTCOLOR )
1369             {
1370                 rAttrib.GetAttr().SetFont(rFont);
1371             }
1372             else
1373             {
1374                 if ( pOutDev )
1375                     pOutDev->SetTextColor( static_cast<const TextAttribFontColor&>(rAttrib.GetAttr()).GetColor() );
1376             }
1377         }
1378     }
1379 
1380     if ( mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) &&
1381         ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) ) )
1382     {
1383         ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
1384         if ( nAttr & ExtTextInputAttr::Underline )
1385             rFont.SetUnderline( LINESTYLE_SINGLE );
1386         else if ( nAttr & ExtTextInputAttr::BoldUnderline )
1387             rFont.SetUnderline( LINESTYLE_BOLD );
1388         else if ( nAttr & ExtTextInputAttr::DottedUnderline )
1389             rFont.SetUnderline( LINESTYLE_DOTTED );
1390         else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
1391             rFont.SetUnderline( LINESTYLE_DOTTED );
1392         if ( nAttr & ExtTextInputAttr::RedText )
1393             rFont.SetColor( COL_RED );
1394         else if ( nAttr & ExtTextInputAttr::HalfToneText )
1395             rFont.SetColor( COL_LIGHTGRAY );
1396         if ( nAttr & ExtTextInputAttr::Highlight )
1397         {
1398             const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
1399             rFont.SetColor( rStyleSettings.GetHighlightTextColor() );
1400             rFont.SetFillColor( rStyleSettings.GetHighlightColor() );
1401             rFont.SetTransparent( false );
1402         }
1403         else if ( nAttr & ExtTextInputAttr::GrayWaveline )
1404         {
1405             rFont.SetUnderline( LINESTYLE_WAVE );
1406 //          if( pOut )
1407 //              pOut->SetTextLineColor( COL_LIGHTGRAY );
1408         }
1409     }
1410 }
1411 
1412 void TextEngine::FormatAndUpdate( TextView* pCurView )
1413 {
1414     if ( mbDowning )
1415         return;
1416 
1417     if ( IsInUndo() )
1418         IdleFormatAndUpdate( pCurView );
1419     else
1420     {
1421         FormatDoc();
1422         UpdateViews( pCurView );
1423     }
1424 }
1425 
1426 void TextEngine::IdleFormatAndUpdate( TextView* pCurView, sal_uInt16 nMaxTimerRestarts )
1427 {
1428     mpIdleFormatter->DoIdleFormat( pCurView, nMaxTimerRestarts );
1429 }
1430 
1431 void TextEngine::TextModified()
1432 {
1433     mbFormatted = false;
1434     mbModified = true;
1435 }
1436 
1437 void TextEngine::UpdateViews( TextView* pCurView )
1438 {
1439     if ( !GetUpdateMode() || IsFormatting() || maInvalidRect.IsEmpty() )
1440         return;
1441 
1442     SAL_WARN_IF( !IsFormatted(), "vcl", "UpdateViews: Doc not formatted!" );
1443 
1444     for (TextView* pView : *mpViews)
1445     {
1446         pView->HideCursor();
1447 
1448         tools::Rectangle aClipRect( maInvalidRect );
1449         const Size aOutSz = pView->GetWindow()->GetOutputSizePixel();
1450         const tools::Rectangle aVisArea( pView->GetStartDocPos(), aOutSz );
1451         aClipRect.Intersection( aVisArea );
1452         if ( !aClipRect.IsEmpty() )
1453         {
1454             // translate into window coordinates
1455             Point aNewPos = pView->GetWindowPos( aClipRect.TopLeft() );
1456             if ( IsRightToLeft() )
1457                 aNewPos.AdjustX( -(aOutSz.Width() - 1) );
1458             aClipRect.SetPos( aNewPos );
1459 
1460             pView->GetWindow()->Invalidate( aClipRect );
1461         }
1462     }
1463 
1464     if ( pCurView )
1465     {
1466         pCurView->ShowCursor( pCurView->IsAutoScroll() );
1467     }
1468 
1469     maInvalidRect = tools::Rectangle();
1470 }
1471 
1472 IMPL_LINK_NOARG(TextEngine, IdleFormatHdl, Timer *, void)
1473 {
1474     FormatAndUpdate( mpIdleFormatter->GetView() );
1475 }
1476 
1477 void TextEngine::CheckIdleFormatter()
1478 {
1479     mpIdleFormatter->ForceTimeout();
1480 }
1481 
1482 void TextEngine::FormatFullDoc()
1483 {
1484     for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
1485     {
1486         TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
1487         pTEParaPortion->MarkSelectionInvalid( 0 );
1488     }
1489     mbFormatted = false;
1490     FormatDoc();
1491 }
1492 
1493 void TextEngine::FormatDoc()
1494 {
1495     if ( IsFormatted() || !GetUpdateMode() || IsFormatting() )
1496         return;
1497 
1498     mbIsFormatting = true;
1499     mbHasMultiLineParas = false;
1500 
1501     long nY = 0;
1502     bool bGrow = false;
1503 
1504     maInvalidRect = tools::Rectangle(); // clear
1505     for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
1506     {
1507         TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1508         if ( pTEParaPortion->IsInvalid() )
1509         {
1510             const long nOldParaWidth = mnCurTextWidth >= 0 ? CalcTextWidth( nPara ) : -1;
1511 
1512             Broadcast( TextHint( SfxHintId::TextFormatPara, nPara ) );
1513 
1514             if ( CreateLines( nPara ) )
1515                 bGrow = true;
1516 
1517             // set InvalidRect only once
1518             if ( maInvalidRect.IsEmpty() )
1519             {
1520                 // otherwise remains Empty() for Paperwidth 0 (AutoPageSize)
1521                 const long nWidth = mnMaxTextWidth
1522                     ? mnMaxTextWidth
1523                     : std::numeric_limits<long>::max();
1524                 const Range aInvRange( GetInvalidYOffsets( nPara ) );
1525                 maInvalidRect = tools::Rectangle( Point( 0, nY+aInvRange.Min() ),
1526                     Size( nWidth, aInvRange.Len() ) );
1527             }
1528             else
1529             {
1530                 maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
1531             }
1532 
1533             if ( mnCurTextWidth >= 0 )
1534             {
1535                 const long nNewParaWidth = CalcTextWidth( nPara );
1536                 if ( nNewParaWidth >= mnCurTextWidth )
1537                     mnCurTextWidth = nNewParaWidth;
1538                 else if ( nOldParaWidth >= mnCurTextWidth )
1539                     mnCurTextWidth = -1;
1540             }
1541         }
1542         else if ( bGrow )
1543         {
1544             maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
1545         }
1546         nY += CalcParaHeight( nPara );
1547         if ( !mbHasMultiLineParas && pTEParaPortion->GetLines().size() > 1 )
1548             mbHasMultiLineParas = true;
1549     }
1550 
1551     if ( !maInvalidRect.IsEmpty() )
1552     {
1553         const long nNewHeight = CalcTextHeight();
1554         const long nDiff = nNewHeight - mnCurTextHeight;
1555         if ( nNewHeight < mnCurTextHeight )
1556         {
1557             maInvalidRect.SetBottom( std::max( nNewHeight, mnCurTextHeight ) );
1558             if ( maInvalidRect.IsEmpty() )
1559             {
1560                 maInvalidRect.SetTop( 0 );
1561                 // Left and Right are not evaluated, but set because of IsEmpty
1562                 maInvalidRect.SetLeft( 0 );
1563                 maInvalidRect.SetRight( mnMaxTextWidth );
1564             }
1565         }
1566 
1567         mnCurTextHeight = nNewHeight;
1568         if ( nDiff )
1569         {
1570             mbFormatted = true;
1571             Broadcast( TextHint( SfxHintId::TextHeightChanged ) );
1572         }
1573     }
1574 
1575     mbIsFormatting = false;
1576     mbFormatted = true;
1577 
1578     Broadcast( TextHint( SfxHintId::TextFormatted ) );
1579 }
1580 
1581 void TextEngine::CreateAndInsertEmptyLine( sal_uInt32 nPara )
1582 {
1583     TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1584     TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1585 
1586     TextLine aTmpLine;
1587     aTmpLine.SetStart( pNode->GetText().getLength() );
1588     aTmpLine.SetEnd( aTmpLine.GetStart() );
1589 
1590     if ( ImpGetAlign() == TxtAlign::Center )
1591         aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth / 2) );
1592     else if ( ImpGetAlign() == TxtAlign::Right )
1593         aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth) );
1594     else
1595         aTmpLine.SetStartX( mpDoc->GetLeftMargin() );
1596 
1597     bool bLineBreak = !pNode->GetText().isEmpty();
1598 
1599     std::unique_ptr<TETextPortion> pDummyPortion(new TETextPortion( 0 ));
1600     pDummyPortion->GetWidth() = 0;
1601     pTEParaPortion->GetTextPortions().push_back( std::move(pDummyPortion) );
1602 
1603     if ( bLineBreak )
1604     {
1605         // -2: The new one is already inserted.
1606         const std::size_t nPos = pTEParaPortion->GetTextPortions().size() - 1;
1607         aTmpLine.SetStartPortion( nPos );
1608         aTmpLine.SetEndPortion( nPos );
1609     }
1610     pTEParaPortion->GetLines().push_back( aTmpLine );
1611 }
1612 
1613 void TextEngine::ImpBreakLine( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nPortionStart, long nRemainingWidth )
1614 {
1615     TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1616 
1617     // Font still should be adjusted
1618     sal_Int32 nMaxBreakPos = mpRefDev->GetTextBreak( pNode->GetText(), nRemainingWidth, nPortionStart );
1619 
1620     SAL_WARN_IF( nMaxBreakPos >= pNode->GetText().getLength(), "vcl", "ImpBreakLine: Break?!" );
1621 
1622     if ( nMaxBreakPos == -1 )   // GetTextBreak() != GetTextSize()
1623         nMaxBreakPos = pNode->GetText().getLength() - 1;
1624 
1625     uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
1626     i18n::LineBreakHyphenationOptions aHyphOptions( nullptr, uno::Sequence< beans::PropertyValue >(), 1 );
1627 
1628     i18n::LineBreakUserOptions aUserOptions;
1629     aUserOptions.forbiddenBeginCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().beginLine;
1630     aUserOptions.forbiddenEndCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().endLine;
1631     aUserOptions.applyForbiddenRules = true;
1632     aUserOptions.allowPunctuationOutsideMargin = false;
1633     aUserOptions.allowHyphenateEnglish = false;
1634 
1635     static const css::lang::Locale aDefLocale;
1636     i18n::LineBreakResults aLBR = xBI->getLineBreak( pNode->GetText(), nMaxBreakPos, aDefLocale, pLine->GetStart(), aHyphOptions, aUserOptions );
1637     sal_Int32 nBreakPos = aLBR.breakIndex;
1638     if ( nBreakPos <= pLine->GetStart() )
1639     {
1640         nBreakPos = nMaxBreakPos;
1641         if ( nBreakPos <= pLine->GetStart() )
1642             nBreakPos = pLine->GetStart() + 1;  // infinite loop otherwise!
1643     }
1644 
1645     // the damaged Portion is the End Portion
1646     pLine->SetEnd( nBreakPos );
1647     const std::size_t nEndPortion = SplitTextPortion( nPara, nBreakPos );
1648 
1649     if ( nBreakPos >= pLine->GetStart() &&
1650          nBreakPos < pNode->GetText().getLength() &&
1651          pNode->GetText()[ nBreakPos ] == ' ' )
1652     {
1653         // generally suppress blanks at the end of line
1654         TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1655         TETextPortion* pTP = pTEParaPortion->GetTextPortions()[ nEndPortion ];
1656         SAL_WARN_IF( nBreakPos <= pLine->GetStart(), "vcl", "ImpBreakLine: SplitTextPortion at beginning of line?" );
1657         pTP->GetWidth() = CalcTextWidth( nPara, nBreakPos-pTP->GetLen(), pTP->GetLen()-1 );
1658     }
1659     pLine->SetEndPortion( nEndPortion );
1660 }
1661 
1662 std::size_t TextEngine::SplitTextPortion( sal_uInt32 nPara, sal_Int32 nPos )
1663 {
1664 
1665     // the Portion at nPos is being split, unless there is already a switch at nPos
1666     if ( nPos == 0 )
1667         return 0;
1668 
1669     std::size_t nSplitPortion;
1670     sal_Int32 nTmpPos = 0;
1671     TETextPortion* pTextPortion = nullptr;
1672     TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1673     const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
1674     for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ )
1675     {
1676         TETextPortion* pTP = pTEParaPortion->GetTextPortions()[nSplitPortion];
1677         nTmpPos += pTP->GetLen();
1678         if ( nTmpPos >= nPos )
1679         {
1680             if ( nTmpPos == nPos )  // nothing needs splitting
1681                 return nSplitPortion;
1682             pTextPortion = pTP;
1683             break;
1684         }
1685     }
1686 
1687     SAL_WARN_IF( !pTextPortion, "vcl", "SplitTextPortion: position outside of region!" );
1688 
1689     const sal_Int32 nOverlapp = nTmpPos - nPos;
1690     pTextPortion->GetLen() -= nOverlapp;
1691     std::unique_ptr<TETextPortion> pNewPortion( new TETextPortion( nOverlapp ) );
1692     pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nSplitPortion + 1, std::move(pNewPortion) );
1693     pTextPortion->GetWidth() = CalcTextWidth( nPara, nPos-pTextPortion->GetLen(), pTextPortion->GetLen() );
1694 
1695     return nSplitPortion;
1696 }
1697 
1698 void TextEngine::CreateTextPortions( sal_uInt32 nPara, sal_Int32 nStartPos )
1699 {
1700     TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1701     TextNode* pNode = pTEParaPortion->GetNode();
1702     SAL_WARN_IF( pNode->GetText().isEmpty(), "vcl", "CreateTextPortions: should not be used for empty paragraphs!" );
1703 
1704     std::set<sal_Int32> aPositions;
1705     std::set<sal_Int32>::iterator aPositionsIt;
1706     aPositions.insert(0);
1707 
1708     const sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
1709     for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
1710     {
1711         TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
1712 
1713         aPositions.insert( rAttrib.GetStart() );
1714         aPositions.insert( rAttrib.GetEnd() );
1715     }
1716     aPositions.insert( pNode->GetText().getLength() );
1717 
1718     const std::vector<TEWritingDirectionInfo>& rWritingDirections = pTEParaPortion->GetWritingDirectionInfos();
1719     for ( const auto& rWritingDirection : rWritingDirections )
1720         aPositions.insert( rWritingDirection.nStartPos );
1721 
1722     if ( mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) )
1723     {
1724         ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xffff);
1725         for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ )
1726         {
1727             if ( mpIMEInfos->pAttribs[n] != nLastAttr )
1728             {
1729                 aPositions.insert( mpIMEInfos->aPos.GetIndex() + n );
1730                 nLastAttr = mpIMEInfos->pAttribs[n];
1731             }
1732         }
1733     }
1734 
1735     sal_Int32 nTabPos = pNode->GetText().indexOf( '\t' );
1736     while ( nTabPos != -1 )
1737     {
1738         aPositions.insert( nTabPos );
1739         aPositions.insert( nTabPos + 1 );
1740         nTabPos = pNode->GetText().indexOf( '\t', nTabPos+1 );
1741     }
1742 
1743     // Delete starting with...
1744     // Unfortunately, the number of TextPortions does not have to be
1745     // equal to aPositions.Count(), because of linebreaks
1746     sal_Int32 nPortionStart = 0;
1747     std::size_t nInvPortion = 0;
1748     std::size_t nP;
1749     for ( nP = 0; nP < pTEParaPortion->GetTextPortions().size(); nP++ )
1750     {
1751         TETextPortion* pTmpPortion = pTEParaPortion->GetTextPortions()[nP];
1752         nPortionStart += pTmpPortion->GetLen();
1753         if ( nPortionStart >= nStartPos )
1754         {
1755             nPortionStart -= pTmpPortion->GetLen();
1756             nInvPortion = nP;
1757             break;
1758         }
1759     }
1760     OSL_ENSURE(nP < pTEParaPortion->GetTextPortions().size()
1761             || pTEParaPortion->GetTextPortions().empty(),
1762             "CreateTextPortions: Nothing to delete!");
1763     if ( nInvPortion && ( nPortionStart+pTEParaPortion->GetTextPortions()[nInvPortion]->GetLen() > nStartPos ) )
1764     {
1765         // better one before...
1766         // But only if it was within the Portion; otherwise it might be
1767         // the only one in the previous line!
1768         nInvPortion--;
1769         nPortionStart -= pTEParaPortion->GetTextPortions()[nInvPortion]->GetLen();
1770     }
1771     pTEParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion );
1772 
1773     // a Portion might have been created by a line break
1774     aPositions.insert( nPortionStart );
1775 
1776     aPositionsIt = aPositions.find( nPortionStart );
1777     SAL_WARN_IF( aPositionsIt == aPositions.end(), "vcl", "CreateTextPortions: nPortionStart not found" );
1778 
1779     if ( aPositionsIt != aPositions.end() )
1780     {
1781         std::set<sal_Int32>::iterator nextIt = aPositionsIt;
1782         for ( ++nextIt; nextIt != aPositions.end(); ++aPositionsIt, ++nextIt )
1783         {
1784             std::unique_ptr<TETextPortion> pNew( new TETextPortion( *nextIt - *aPositionsIt ) );
1785             pTEParaPortion->GetTextPortions().push_back( std::move(pNew) );
1786         }
1787     }
1788     OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "CreateTextPortions: No Portions?!");
1789 }
1790 
1791 void TextEngine::RecalcTextPortion( sal_uInt32 nPara, sal_Int32 nStartPos, sal_Int32 nNewChars )
1792 {
1793     TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1794     OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "RecalcTextPortion: no Portions!");
1795     OSL_ENSURE(nNewChars, "RecalcTextPortion: Diff == 0");
1796 
1797     TextNode* const pNode = pTEParaPortion->GetNode();
1798     if ( nNewChars > 0 )
1799     {
1800         // If an Attribute is starting/ending at nStartPos, or there is a tab
1801         // before nStartPos => a new Portion starts.
1802         // Otherwise the Portion is extended at nStartPos.
1803         // Or if at the very beginning ( StartPos 0 ) followed by a tab...
1804         if ( ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) ) ||
1805              ( nStartPos && ( pNode->GetText()[ nStartPos - 1 ] == '\t' ) ) ||
1806              ( !nStartPos && ( nNewChars < pNode->GetText().getLength() ) && pNode->GetText()[ nNewChars ] == '\t' ) )
1807         {
1808             std::size_t nNewPortionPos = 0;
1809             if ( nStartPos )
1810                 nNewPortionPos = SplitTextPortion( nPara, nStartPos ) + 1;
1811 
1812             // Here could be an empty Portion if the paragraph was empty,
1813             // or a new line was created by a hard line-break.
1814             if ( ( nNewPortionPos < pTEParaPortion->GetTextPortions().size() ) &&
1815                     !pTEParaPortion->GetTextPortions()[nNewPortionPos]->GetLen() )
1816             {
1817                 // use the empty Portion
1818                 pTEParaPortion->GetTextPortions()[nNewPortionPos]->GetLen() = nNewChars;
1819             }
1820             else
1821             {
1822                 std::unique_ptr<TETextPortion> pNewPortion(new TETextPortion( nNewChars ));
1823                 pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nNewPortionPos, std::move(pNewPortion) );
1824             }
1825         }
1826         else
1827         {
1828             sal_Int32 nPortionStart {0};
1829             const std::size_t nTP = pTEParaPortion->GetTextPortions().FindPortion( nStartPos, nPortionStart );
1830             TETextPortion* const pTP = pTEParaPortion->GetTextPortions()[ nTP ];
1831             SAL_WARN_IF( !pTP, "vcl", "RecalcTextPortion: Portion not found!"  );
1832             pTP->GetLen() += nNewChars;
1833             pTP->GetWidth() = -1;
1834         }
1835     }
1836     else
1837     {
1838         // Shrink or remove Portion
1839         // Before calling this function, ensure that no Portions were in the deleted range!
1840 
1841         // There must be no Portion reaching into or starting within,
1842         // thus: nStartPos <= nPos <= nStartPos - nNewChars(neg.)
1843         std::size_t nPortion = 0;
1844         sal_Int32 nPos = 0;
1845         const sal_Int32 nEnd = nStartPos-nNewChars;
1846         const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
1847         TETextPortion* pTP = nullptr;
1848         for ( nPortion = 0; nPortion < nPortions; nPortion++ )
1849         {
1850             pTP = pTEParaPortion->GetTextPortions()[ nPortion ];
1851             if ( ( nPos+pTP->GetLen() ) > nStartPos )
1852             {
1853                 SAL_WARN_IF( nPos > nStartPos, "vcl", "RecalcTextPortion: Bad Start!" );
1854                 SAL_WARN_IF( nPos+pTP->GetLen() < nEnd, "vcl", "RecalcTextPortion: Bad End!" );
1855                 break;
1856             }
1857             nPos += pTP->GetLen();
1858         }
1859         SAL_WARN_IF( !pTP, "vcl", "RecalcTextPortion: Portion not found!" );
1860         if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
1861         {
1862             // remove Portion
1863             pTEParaPortion->GetTextPortions().erase( pTEParaPortion->GetTextPortions().begin() + nPortion );
1864         }
1865         else
1866         {
1867             SAL_WARN_IF( pTP->GetLen() <= (-nNewChars), "vcl", "RecalcTextPortion: Portion too small to shrink!" );
1868             pTP->GetLen() += nNewChars;
1869         }
1870         OSL_ENSURE( pTEParaPortion->GetTextPortions().size(),
1871                 "RecalcTextPortion: none are left!" );
1872     }
1873 }
1874 
1875 void TextEngine::ImpPaint( OutputDevice* pOutDev, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection )
1876 {
1877     if ( !GetUpdateMode() )
1878         return;
1879 
1880     if ( !IsFormatted() )
1881         FormatDoc();
1882 
1883     vcl::Window* const pOutWin = dynamic_cast<vcl::Window*>(pOutDev);
1884     const bool bTransparent = (pOutWin && pOutWin->IsPaintTransparent());
1885 
1886     long nY = rStartPos.Y();
1887 
1888     TextPaM const* pSelStart = nullptr;
1889     TextPaM const* pSelEnd = nullptr;
1890     if ( pSelection && pSelection->HasRange() )
1891     {
1892         const bool bInvers = pSelection->GetEnd() < pSelection->GetStart();
1893         pSelStart = !bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
1894         pSelEnd = bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
1895     }
1896 
1897     const StyleSettings& rStyleSettings = pOutDev->GetSettings().GetStyleSettings();
1898 
1899     // for all paragraphs
1900     for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
1901     {
1902         TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
1903         // in case while typing Idle-Formatting, asynchronous Paint
1904         if ( pPortion->IsInvalid() )
1905             return;
1906 
1907         const long nParaHeight = CalcParaHeight( nPara );
1908         if ( !pPaintArea || ( ( nY + nParaHeight ) > pPaintArea->Top() ) )
1909         {
1910             // for all lines of the paragraph
1911             sal_Int32 nIndex = 0;
1912             for ( auto & rLine : pPortion->GetLines() )
1913             {
1914                 Point aTmpPos( rStartPos.X() + rLine.GetStartX(), nY );
1915 
1916                 if ( !pPaintArea || ( ( nY + mnCharHeight ) > pPaintArea->Top() ) )
1917                 {
1918                     // for all Portions of the line
1919                     nIndex = rLine.GetStart();
1920                     for ( std::size_t y = rLine.GetStartPortion(); y <= rLine.GetEndPortion(); y++ )
1921                     {
1922                         OSL_ENSURE(pPortion->GetTextPortions().size(),
1923                                 "ImpPaint: Line without Textportion!");
1924                         TETextPortion* pTextPortion = pPortion->GetTextPortions()[ y ];
1925                         SAL_WARN_IF( !pTextPortion, "vcl", "ImpPaint: Bad pTextPortion!" );
1926 
1927                         ImpInitLayoutMode( pOutDev /*, pTextPortion->IsRightToLeft() */);
1928 
1929                         const long nTxtWidth = pTextPortion->GetWidth();
1930                         aTmpPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nIndex, nIndex ) );
1931 
1932                         // only print if starting in the visible region
1933                         if ( ( aTmpPos.X() + nTxtWidth ) >= 0 )
1934                         {
1935                             switch ( pTextPortion->GetKind() )
1936                             {
1937                                 case PORTIONKIND_TEXT:
1938                                     {
1939                                         vcl::Font aFont;
1940                                         SeekCursor( nPara, nIndex+1, aFont, pOutDev );
1941                                         if( bTransparent )
1942                                             aFont.SetTransparent( true );
1943                                         else if ( pSelection )
1944                                             aFont.SetTransparent( false );
1945                                         pOutDev->SetFont( aFont );
1946 
1947                                         sal_Int32 nTmpIndex = nIndex;
1948                                         sal_Int32 nEnd = nTmpIndex + pTextPortion->GetLen();
1949                                         Point aPos = aTmpPos;
1950 
1951                                         bool bDone = false;
1952                                         if ( pSelStart )
1953                                         {
1954                                             // is a part of it in the selection?
1955                                             const TextPaM aTextStart( nPara, nTmpIndex );
1956                                             const TextPaM aTextEnd( nPara, nEnd );
1957                                             if ( ( aTextStart < *pSelEnd ) && ( aTextEnd > *pSelStart ) )
1958                                             {
1959                                                 // 1) vcl::Region before Selection
1960                                                 if ( aTextStart < *pSelStart )
1961                                                 {
1962                                                     const sal_Int32 nL = pSelStart->GetIndex() - nTmpIndex;
1963                                                     pOutDev->SetFont( aFont);
1964                                                     pOutDev->SetTextFillColor();
1965                                                     aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
1966                                                     pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
1967                                                     nTmpIndex = nTmpIndex + nL;
1968 
1969                                                 }
1970                                                 // 2) vcl::Region with Selection
1971                                                 sal_Int32 nL = nEnd - nTmpIndex;
1972                                                 if ( aTextEnd > *pSelEnd )
1973                                                     nL = pSelEnd->GetIndex() - nTmpIndex;
1974                                                 if ( nL )
1975                                                 {
1976                                                     const Color aOldTextColor = pOutDev->GetTextColor();
1977                                                     pOutDev->SetTextColor( rStyleSettings.GetHighlightTextColor() );
1978                                                     pOutDev->SetTextFillColor( rStyleSettings.GetHighlightColor() );
1979                                                     aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
1980                                                     pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
1981                                                     pOutDev->SetTextColor( aOldTextColor );
1982                                                     pOutDev->SetTextFillColor();
1983                                                     nTmpIndex = nTmpIndex + nL;
1984                                                 }
1985 
1986                                                 // 3) vcl::Region after Selection
1987                                                 if ( nTmpIndex < nEnd )
1988                                                 {
1989                                                     nL = nEnd-nTmpIndex;
1990                                                     pOutDev->SetTextFillColor();
1991                                                     aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
1992                                                     pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
1993                                                 }
1994                                                 bDone = true;
1995                                             }
1996                                         }
1997                                         if ( !bDone )
1998                                         {
1999                                             pOutDev->SetTextFillColor();
2000                                             aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nEnd ) );
2001                                             pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
2002                                         }
2003                                     }
2004                                     break;
2005                                 case PORTIONKIND_TAB:
2006                                     // for HideSelection() only Range, pSelection = 0.
2007                                     if ( pSelStart ) // also implies pSelEnd
2008                                     {
2009                                         const tools::Rectangle aTabArea( aTmpPos, Point( aTmpPos.X()+nTxtWidth, aTmpPos.Y()+mnCharHeight-1 ) );
2010                                         // is the Tab in the Selection???
2011                                         const TextPaM aTextStart(nPara, nIndex);
2012                                         const TextPaM aTextEnd(nPara, nIndex + 1);
2013                                         if ((aTextStart < *pSelEnd) && (aTextEnd > *pSelStart))
2014                                         {
2015                                             const Color aOldColor = pOutDev->GetFillColor();
2016                                             pOutDev->SetFillColor(
2017                                                 rStyleSettings.GetHighlightColor());
2018                                             pOutDev->DrawRect(aTabArea);
2019                                             pOutDev->SetFillColor(aOldColor);
2020                                         }
2021                                         else
2022                                         {
2023                                             pOutDev->Erase( aTabArea );
2024                                         }
2025                                     }
2026                                     break;
2027                                 default:
2028                                     OSL_FAIL( "ImpPaint: Unknown Portion-Type !" );
2029                             }
2030                         }
2031 
2032                         nIndex += pTextPortion->GetLen();
2033                     }
2034                 }
2035 
2036                 nY += mnCharHeight;
2037 
2038                 if ( pPaintArea && ( nY >= pPaintArea->Bottom() ) )
2039                     break;  // no more visible actions
2040             }
2041         }
2042         else
2043         {
2044             nY += nParaHeight;
2045         }
2046 
2047         if ( pPaintArea && ( nY > pPaintArea->Bottom() ) )
2048             break;  // no more visible actions
2049     }
2050 }
2051 
2052 bool TextEngine::CreateLines( sal_uInt32 nPara )
2053 {
2054     // bool: changing Height of Paragraph Yes/No - true/false
2055 
2056     TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2057     TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
2058     SAL_WARN_IF( !pTEParaPortion->IsInvalid(), "vcl", "CreateLines: Portion not invalid!" );
2059 
2060     const auto nOldLineCount = pTEParaPortion->GetLines().size();
2061 
2062     // fast special case for empty paragraphs
2063     if ( pTEParaPortion->GetNode()->GetText().isEmpty() )
2064     {
2065         if ( !pTEParaPortion->GetTextPortions().empty() )
2066             pTEParaPortion->GetTextPortions().Reset();
2067         pTEParaPortion->GetLines().clear();
2068         CreateAndInsertEmptyLine( nPara );
2069         pTEParaPortion->SetValid();
2070         return nOldLineCount != pTEParaPortion->GetLines().size();
2071     }
2072 
2073     // initialization
2074     if ( pTEParaPortion->GetLines().empty() )
2075     {
2076         pTEParaPortion->GetLines().emplace_back( );
2077     }
2078 
2079     const sal_Int32 nInvalidDiff = pTEParaPortion->GetInvalidDiff();
2080     const sal_Int32 nInvalidStart = pTEParaPortion->GetInvalidPosStart();
2081     const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff );
2082     bool bQuickFormat = false;
2083 
2084     if ( pTEParaPortion->GetWritingDirectionInfos().empty() )
2085         ImpInitWritingDirections( nPara );
2086 
2087     if ( pTEParaPortion->GetWritingDirectionInfos().size() == 1 && pTEParaPortion->IsSimpleInvalid() )
2088     {
2089         bQuickFormat = nInvalidDiff != 0;
2090         if ( nInvalidDiff < 0 )
2091         {
2092             // check if deleting across Portion border
2093             sal_Int32 nPos = 0;
2094             for ( const auto & pTP : pTEParaPortion->GetTextPortions() )
2095             {
2096                 // there must be no Start/End in the deleted region
2097                 nPos += pTP->GetLen();
2098                 if ( nPos > nInvalidStart && nPos < nInvalidEnd )
2099                 {
2100                     bQuickFormat = false;
2101                     break;
2102                 }
2103             }
2104         }
2105     }
2106 
2107     if ( bQuickFormat )
2108         RecalcTextPortion( nPara, nInvalidStart, nInvalidDiff );
2109     else
2110         CreateTextPortions( nPara, nInvalidStart );
2111 
2112     // search for line with InvalidPos; start a line prior
2113     // flag lines => do not remove!
2114 
2115     sal_uInt16 nLine = pTEParaPortion->GetLines().size()-1;
2116     for ( sal_uInt16 nL = 0; nL <= nLine; nL++ )
2117     {
2118         TextLine& rLine = pTEParaPortion->GetLines()[ nL ];
2119         if ( rLine.GetEnd() > nInvalidStart )
2120         {
2121             nLine = nL;
2122             break;
2123         }
2124         rLine.SetValid();
2125     }
2126     // start a line before...
2127     // if typing at the end, the line before cannot change
2128     if ( nLine && ( !pTEParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->GetText().getLength() ) || ( nInvalidDiff <= 0 ) ) )
2129         nLine--;
2130 
2131     TextLine* pLine =  &( pTEParaPortion->GetLines()[ nLine ] );
2132 
2133     // format all lines starting here
2134     std::size_t nDelFromLine = TETextPortionList::npos;
2135 
2136     sal_Int32 nIndex = pLine->GetStart();
2137     TextLine aSaveLine( *pLine );
2138 
2139     vcl::Font aFont;
2140 
2141     while ( nIndex < pNode->GetText().getLength() )
2142     {
2143         bool bEOL = false;
2144         sal_Int32 nPortionStart = 0;
2145         sal_Int32 nPortionEnd = 0;
2146 
2147         sal_Int32 nTmpPos = nIndex;
2148         std::size_t nTmpPortion = pLine->GetStartPortion();
2149         long nTmpWidth = mpDoc->GetLeftMargin();
2150         // do not subtract margin; it is included in TmpWidth
2151         long nXWidth = std::max(
2152             mnMaxTextWidth ? mnMaxTextWidth : std::numeric_limits<long>::max(), nTmpWidth);
2153 
2154         // search for Portion that does not fit anymore into line
2155         TETextPortion* pPortion = nullptr;
2156         bool bBrokenLine = false;
2157 
2158         while ( ( nTmpWidth <= nXWidth ) && !bEOL && ( nTmpPortion < pTEParaPortion->GetTextPortions().size() ) )
2159         {
2160             nPortionStart = nTmpPos;
2161             pPortion = pTEParaPortion->GetTextPortions()[ nTmpPortion ];
2162             SAL_WARN_IF( !pPortion->GetLen(), "vcl", "CreateLines: Empty Portion!" );
2163             if ( pNode->GetText()[ nTmpPos ] == '\t' )
2164             {
2165                 long nCurPos = nTmpWidth-mpDoc->GetLeftMargin();
2166                 nTmpWidth = ((nCurPos/mnDefTab)+1)*mnDefTab+mpDoc->GetLeftMargin();
2167                 pPortion->GetWidth() = nTmpWidth - nCurPos - mpDoc->GetLeftMargin();
2168                 // infinite loop, if this is the first token of the line and nTmpWidth > aPaperSize.Width !!!
2169                 if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
2170                 {
2171                     // adjust Tab
2172                     pPortion->GetWidth() = nXWidth-1;
2173                     nTmpWidth = pPortion->GetWidth();
2174                     bEOL = true;
2175                     bBrokenLine = true;
2176                 }
2177                 pPortion->GetKind() = PORTIONKIND_TAB;
2178             }
2179             else
2180             {
2181 
2182                 pPortion->GetWidth() = CalcTextWidth( nPara, nTmpPos, pPortion->GetLen() );
2183                 nTmpWidth += pPortion->GetWidth();
2184 
2185                 pPortion->SetRightToLeft( ImpGetRightToLeft( nPara, nTmpPos+1 ) );
2186                 pPortion->GetKind() = PORTIONKIND_TEXT;
2187             }
2188 
2189             nTmpPos += pPortion->GetLen();
2190             nPortionEnd = nTmpPos;
2191             nTmpPortion++;
2192         }
2193 
2194         // this was perhaps one Portion too far
2195         bool bFixedEnd = false;
2196         if ( nTmpWidth > nXWidth )
2197         {
2198             nPortionEnd = nTmpPos;
2199             nTmpPos -= pPortion->GetLen();
2200             nPortionStart = nTmpPos;
2201             nTmpPortion--;
2202             bEOL = false;
2203 
2204             nTmpWidth -= pPortion->GetWidth();
2205             if ( pPortion->GetKind() == PORTIONKIND_TAB )
2206             {
2207                 bEOL = true;
2208                 bFixedEnd = true;
2209             }
2210         }
2211         else
2212         {
2213             bEOL = true;
2214             pLine->SetEnd( nPortionEnd );
2215             OSL_ENSURE(pTEParaPortion->GetTextPortions().size(),
2216                     "CreateLines: No TextPortions?");
2217             pLine->SetEndPortion( pTEParaPortion->GetTextPortions().size() - 1 );
2218         }
2219 
2220         if ( bFixedEnd )
2221         {
2222             pLine->SetEnd( nPortionStart );
2223             pLine->SetEndPortion( nTmpPortion-1 );
2224         }
2225         else if ( bBrokenLine )
2226         {
2227             pLine->SetEnd( nPortionStart+1 );
2228             pLine->SetEndPortion( nTmpPortion-1 );
2229         }
2230         else if ( !bEOL )
2231         {
2232             SAL_WARN_IF( (nPortionEnd-nPortionStart) != pPortion->GetLen(), "vcl", "CreateLines: There is a Portion after all?!" );
2233             const long nRemainingWidth = mnMaxTextWidth - nTmpWidth;
2234             ImpBreakLine( nPara, pLine, nPortionStart, nRemainingWidth );
2235         }
2236 
2237         if ( ( ImpGetAlign() == TxtAlign::Center ) || ( ImpGetAlign() == TxtAlign::Right ) )
2238         {
2239             // adjust
2240             long nTextWidth = 0;
2241             for ( std::size_t nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ )
2242             {
2243                 TETextPortion* pTextPortion = pTEParaPortion->GetTextPortions()[ nTP ];
2244                 nTextWidth += pTextPortion->GetWidth();
2245             }
2246             const long nSpace = mnMaxTextWidth - nTextWidth;
2247             if ( nSpace > 0 )
2248             {
2249                 if ( ImpGetAlign() == TxtAlign::Center )
2250                     pLine->SetStartX( static_cast<sal_uInt16>(nSpace / 2) );
2251                 else    // TxtAlign::Right
2252                     pLine->SetStartX( static_cast<sal_uInt16>(nSpace) );
2253             }
2254         }
2255         else
2256         {
2257             pLine->SetStartX( mpDoc->GetLeftMargin() );
2258         }
2259 
2260          // check if the line has to be printed again
2261         pLine->SetInvalid();
2262 
2263         if ( pTEParaPortion->IsSimpleInvalid() )
2264         {
2265             // Change due to simple TextChange...
2266             // Do not abort formatting, as Portions might have to be split!
2267             // Once it is ok to abort, then validate the following lines!
2268             // But mark as valid, thus reduce printing...
2269             if ( pLine->GetEnd() < nInvalidStart )
2270             {
2271                 if ( *pLine == aSaveLine )
2272                 {
2273                     pLine->SetValid();
2274                 }
2275             }
2276             else
2277             {
2278                 const sal_Int32 nStart = pLine->GetStart();
2279                 const sal_Int32 nEnd = pLine->GetEnd();
2280 
2281                 if ( nStart > nInvalidEnd )
2282                 {
2283                     if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) &&
2284                             ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) )
2285                     {
2286                         pLine->SetValid();
2287                         if ( bQuickFormat )
2288                         {
2289                             pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
2290                             break;
2291                         }
2292                     }
2293                 }
2294                 else if ( bQuickFormat && ( nEnd > nInvalidEnd) )
2295                 {
2296                     // If the invalid line ends such that the next line starts
2297                     // at the 'same' position as before (no change in line breaks),
2298                     // the text width does not have to be recalculated.
2299                     if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) )
2300                     {
2301                         pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
2302                         break;
2303                     }
2304                 }
2305             }
2306         }
2307 
2308         nIndex = pLine->GetEnd();   // next line Start = previous line End
2309                                     // because nEnd is past the last char!
2310 
2311         const std::size_t nEndPortion = pLine->GetEndPortion();
2312 
2313         // next line or new line
2314         pLine = nullptr;
2315         if ( nLine < pTEParaPortion->GetLines().size()-1 )
2316             pLine = &( pTEParaPortion->GetLines()[ ++nLine ] );
2317         if ( pLine && ( nIndex >= pNode->GetText().getLength() ) )
2318         {
2319             nDelFromLine = nLine;
2320             break;
2321         }
2322         if ( !pLine )
2323         {
2324             if ( nIndex < pNode->GetText().getLength() )
2325             {
2326                 ++nLine;
2327                 pTEParaPortion->GetLines().insert( pTEParaPortion->GetLines().begin() + nLine, TextLine() );
2328                 pLine = &pTEParaPortion->GetLines()[nLine];
2329             }
2330             else
2331             {
2332                 break;
2333             }
2334         }
2335         aSaveLine = *pLine;
2336         pLine->SetStart( nIndex );
2337         pLine->SetEnd( nIndex );
2338         pLine->SetStartPortion( nEndPortion+1 );
2339         pLine->SetEndPortion( nEndPortion+1 );
2340 
2341     }   // while ( Index < Len )
2342 
2343     if (nDelFromLine != TETextPortionList::npos)
2344     {
2345         pTEParaPortion->GetLines().erase( pTEParaPortion->GetLines().begin() + nDelFromLine,
2346                                           pTEParaPortion->GetLines().end() );
2347     }
2348 
2349     SAL_WARN_IF( pTEParaPortion->GetLines().empty(), "vcl", "CreateLines: No Line!" );
2350 
2351     pTEParaPortion->SetValid();
2352 
2353     return nOldLineCount != pTEParaPortion->GetLines().size();
2354 }
2355 
2356 OUString TextEngine::GetWord( const TextPaM& rCursorPos, TextPaM* pStartOfWord )
2357 {
2358     OUString aWord;
2359     if ( rCursorPos.GetPara() < mpDoc->GetNodes().size() )
2360     {
2361         TextSelection aSel( rCursorPos );
2362         TextNode* pNode = mpDoc->GetNodes()[ rCursorPos.GetPara() ].get();
2363         uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
2364         i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), rCursorPos.GetIndex(), GetLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
2365         aSel.GetStart().GetIndex() = aBoundary.startPos;
2366         aSel.GetEnd().GetIndex() = aBoundary.endPos;
2367         aWord = pNode->GetText().copy( aSel.GetStart().GetIndex(), aSel.GetEnd().GetIndex() - aSel.GetStart().GetIndex() );
2368         if ( pStartOfWord )
2369             *pStartOfWord = aSel.GetStart();
2370     }
2371     return aWord;
2372 }
2373 
2374 bool TextEngine::Read( SvStream& rInput, const TextSelection* pSel )
2375 {
2376     const bool bUpdate = GetUpdateMode();
2377     SetUpdateMode( false );
2378 
2379     UndoActionStart();
2380     TextSelection aSel;
2381     if ( pSel )
2382         aSel = *pSel;
2383     else
2384     {
2385         const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2386         TextNode* pNode = mpDoc->GetNodes()[ nParas - 1 ].get();
2387         aSel = TextPaM( nParas-1 , pNode->GetText().getLength() );
2388     }
2389 
2390     if ( aSel.HasRange() )
2391         aSel = ImpDeleteText( aSel );
2392 
2393     OString aLine;
2394     bool bDone = rInput.ReadLine( aLine );
2395     OUString aTmpStr(OStringToOUString(aLine, rInput.GetStreamCharSet()));
2396     while ( bDone )
2397     {
2398         aSel = ImpInsertText( aSel, aTmpStr );
2399         bDone = rInput.ReadLine( aLine );
2400         aTmpStr = OStringToOUString(aLine, rInput.GetStreamCharSet());
2401         if ( bDone )
2402             aSel = ImpInsertParaBreak( aSel.GetEnd() );
2403     }
2404 
2405     UndoActionEnd();
2406 
2407     const TextSelection aNewSel( aSel.GetEnd(), aSel.GetEnd() );
2408 
2409     // so that FormatAndUpdate does not access the invalid selection
2410     if ( GetActiveView() )
2411         GetActiveView()->ImpSetSelection( aNewSel );
2412 
2413     SetUpdateMode( bUpdate );
2414     FormatAndUpdate( GetActiveView() );
2415 
2416     return rInput.GetError() == ERRCODE_NONE;
2417 }
2418 
2419 void TextEngine::Write( SvStream& rOutput )
2420 {
2421     TextSelection aSel;
2422     const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2423     TextNode* pSelNode = mpDoc->GetNodes()[ nParas - 1 ].get();
2424     aSel.GetStart() = TextPaM( 0, 0 );
2425     aSel.GetEnd() = TextPaM( nParas-1, pSelNode->GetText().getLength() );
2426 
2427     for ( sal_uInt32 nPara = aSel.GetStart().GetPara(); nPara <= aSel.GetEnd().GetPara(); ++nPara  )
2428     {
2429         TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2430 
2431         const sal_Int32 nStartPos = nPara == aSel.GetStart().GetPara()
2432             ? aSel.GetStart().GetIndex() : 0;
2433         const sal_Int32 nEndPos = nPara == aSel.GetEnd().GetPara()
2434             ? aSel.GetEnd().GetIndex() : pNode->GetText().getLength();
2435 
2436         const OUString aText = pNode->GetText().copy( nStartPos, nEndPos-nStartPos );
2437         rOutput.WriteLine(OUStringToOString(aText, rOutput.GetStreamCharSet()));
2438     }
2439 }
2440 
2441 void TextEngine::RemoveAttribs( sal_uInt32 nPara )
2442 {
2443     if ( nPara < mpDoc->GetNodes().size() )
2444     {
2445         TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2446         if ( pNode->GetCharAttribs().Count() )
2447         {
2448             pNode->GetCharAttribs().Clear();
2449 
2450             TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
2451             pTEParaPortion->MarkSelectionInvalid( 0 );
2452 
2453             mbFormatted = false;
2454 
2455             IdleFormatAndUpdate( nullptr, 0xFFFF );
2456         }
2457     }
2458 }
2459 
2460 void TextEngine::SetAttrib( const TextAttrib& rAttr, sal_uInt32 nPara, sal_Int32 nStart, sal_Int32 nEnd, bool bIdleFormatAndUpdate )
2461 {
2462 
2463     // For now do not check if Attributes overlap!
2464     // This function is for TextEditors that want to _quickly_ generate the Syntax-Highlight
2465 
2466     // As TextEngine is currently intended only for TextEditors, there is no Undo for Attributes!
2467 
2468     if ( nPara < mpDoc->GetNodes().size() )
2469     {
2470         TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2471         TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
2472 
2473         const sal_Int32 nMax = pNode->GetText().getLength();
2474         if ( nStart > nMax )
2475             nStart = nMax;
2476         if ( nEnd > nMax )
2477             nEnd = nMax;
2478 
2479         pNode->GetCharAttribs().InsertAttrib( std::make_unique<TextCharAttrib>( rAttr, nStart, nEnd ) );
2480         pTEParaPortion->MarkSelectionInvalid( nStart );
2481 
2482         mbFormatted = false;
2483         if ( bIdleFormatAndUpdate )
2484             IdleFormatAndUpdate( nullptr, 0xFFFF );
2485         else
2486             FormatAndUpdate();
2487     }
2488 }
2489 
2490 void TextEngine::SetTextAlign( TxtAlign eAlign )
2491 {
2492     if ( eAlign != meAlign )
2493     {
2494         meAlign = eAlign;
2495         FormatFullDoc();
2496         UpdateViews();
2497     }
2498 }
2499 
2500 void TextEngine::ValidateSelection( TextSelection& rSel ) const
2501 {
2502     ValidatePaM( rSel.GetStart() );
2503     ValidatePaM( rSel.GetEnd() );
2504 }
2505 
2506 void TextEngine::ValidatePaM( TextPaM& rPaM ) const
2507 {
2508     const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2509     if ( rPaM.GetPara() >= nParas )
2510     {
2511         rPaM.GetPara() = nParas ? nParas-1 : 0;
2512         rPaM.GetIndex() = TEXT_INDEX_ALL;
2513     }
2514 
2515     const sal_Int32 nMaxIndex = GetTextLen( rPaM.GetPara() );
2516     if ( rPaM.GetIndex() > nMaxIndex )
2517         rPaM.GetIndex() = nMaxIndex;
2518 }
2519 
2520 // adjust State & Selection
2521 
2522 void TextEngine::ImpParagraphInserted( sal_uInt32 nPara )
2523 {
2524     // No adjustment needed for the active View;
2525     // but for all passive Views the Selection needs adjusting.
2526     if ( mpViews->size() > 1 )
2527     {
2528         for ( auto nView = mpViews->size(); nView; )
2529         {
2530             TextView* pView = (*mpViews)[ --nView ];
2531             if ( pView != GetActiveView() )
2532             {
2533                 for ( int n = 0; n <= 1; n++ )
2534                 {
2535                     TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2536                     if ( rPaM.GetPara() >= nPara )
2537                         rPaM.GetPara()++;
2538                 }
2539             }
2540         }
2541     }
2542     Broadcast( TextHint( SfxHintId::TextParaInserted, nPara ) );
2543 }
2544 
2545 void TextEngine::ImpParagraphRemoved( sal_uInt32 nPara )
2546 {
2547     if ( mpViews->size() > 1 )
2548     {
2549         for ( auto nView = mpViews->size(); nView; )
2550         {
2551             TextView* pView = (*mpViews)[ --nView ];
2552             if ( pView != GetActiveView() )
2553             {
2554                 const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2555                 for ( int n = 0; n <= 1; n++ )
2556                 {
2557                     TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2558                     if ( rPaM.GetPara() > nPara )
2559                         rPaM.GetPara()--;
2560                     else if ( rPaM.GetPara() == nPara )
2561                     {
2562                         rPaM.GetIndex() = 0;
2563                         if ( rPaM.GetPara() >= nParas )
2564                             rPaM.GetPara()--;
2565                     }
2566                 }
2567             }
2568         }
2569     }
2570     Broadcast( TextHint( SfxHintId::TextParaRemoved, nPara ) );
2571 }
2572 
2573 void TextEngine::ImpCharsRemoved( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
2574 {
2575     if ( mpViews->size() > 1 )
2576     {
2577         for ( auto nView = mpViews->size(); nView; )
2578         {
2579             TextView* pView = (*mpViews)[ --nView ];
2580             if ( pView != GetActiveView() )
2581             {
2582                 const sal_Int32 nEnd = nPos + nChars;
2583                 for ( int n = 0; n <= 1; n++ )
2584                 {
2585                     TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2586                     if ( rPaM.GetPara() == nPara )
2587                     {
2588                         if ( rPaM.GetIndex() > nEnd )
2589                             rPaM.GetIndex() = rPaM.GetIndex() - nChars;
2590                         else if ( rPaM.GetIndex() > nPos )
2591                             rPaM.GetIndex() = nPos;
2592                     }
2593                 }
2594             }
2595         }
2596     }
2597     Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
2598 }
2599 
2600 void TextEngine::ImpCharsInserted( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
2601 {
2602     if ( mpViews->size() > 1 )
2603     {
2604         for ( auto nView = mpViews->size(); nView; )
2605         {
2606             TextView* pView = (*mpViews)[ --nView ];
2607             if ( pView != GetActiveView() )
2608             {
2609                 for ( int n = 0; n <= 1; n++ )
2610                 {
2611                     TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2612                     if ( rPaM.GetPara() == nPara )
2613                     {
2614                         if ( rPaM.GetIndex() >= nPos )
2615                             rPaM.GetIndex() += nChars;
2616                     }
2617                 }
2618             }
2619         }
2620     }
2621     Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
2622 }
2623 
2624 void TextEngine::Draw( OutputDevice* pDev, const Point& rPos )
2625 {
2626     ImpPaint( pDev, rPos, nullptr );
2627 }
2628 
2629 void TextEngine::SetLeftMargin( sal_uInt16 n )
2630 {
2631     mpDoc->SetLeftMargin( n );
2632 }
2633 
2634 uno::Reference< i18n::XBreakIterator > const & TextEngine::GetBreakIterator()
2635 {
2636     if ( !mxBreakIterator.is() )
2637         mxBreakIterator = vcl::unohelper::CreateBreakIterator();
2638     SAL_WARN_IF( !mxBreakIterator.is(), "vcl", "BreakIterator: Failed to create!" );
2639     return mxBreakIterator;
2640 }
2641 
2642 void TextEngine::SetLocale( const css::lang::Locale& rLocale )
2643 {
2644     maLocale = rLocale;
2645     mpLocaleDataWrapper.reset();
2646 }
2647 
2648 css::lang::Locale const & TextEngine::GetLocale()
2649 {
2650     if ( maLocale.Language.isEmpty() )
2651     {
2652         maLocale = Application::GetSettings().GetUILanguageTag().getLocale();   // TODO: why UI locale?
2653     }
2654     return maLocale;
2655 }
2656 
2657 LocaleDataWrapper* TextEngine::ImpGetLocaleDataWrapper()
2658 {
2659     if ( !mpLocaleDataWrapper )
2660         mpLocaleDataWrapper.reset( new LocaleDataWrapper( LanguageTag( GetLocale()) ) );
2661 
2662     return mpLocaleDataWrapper.get();
2663 }
2664 
2665 void TextEngine::SetRightToLeft( bool bR2L )
2666 {
2667     if ( mbRightToLeft != bR2L )
2668     {
2669         mbRightToLeft = bR2L;
2670         meAlign = bR2L ? TxtAlign::Right : TxtAlign::Left;
2671         FormatFullDoc();
2672         UpdateViews();
2673     }
2674 }
2675 
2676 void TextEngine::ImpInitWritingDirections( sal_uInt32 nPara )
2677 {
2678     TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
2679     std::vector<TEWritingDirectionInfo>& rInfos = pParaPortion->GetWritingDirectionInfos();
2680     rInfos.clear();
2681 
2682     if ( !pParaPortion->GetNode()->GetText().isEmpty() )
2683     {
2684         const UBiDiLevel nBidiLevel = IsRightToLeft() ? 1 /*RTL*/ : 0 /*LTR*/;
2685         OUString aText( pParaPortion->GetNode()->GetText() );
2686 
2687         // Bidi functions from icu 2.0
2688 
2689         UErrorCode nError = U_ZERO_ERROR;
2690         UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError );
2691         nError = U_ZERO_ERROR;
2692 
2693         ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError );
2694         nError = U_ZERO_ERROR;
2695 
2696         long nCount = ubidi_countRuns( pBidi, &nError );
2697 
2698         int32_t nStart = 0;
2699         int32_t nEnd;
2700         UBiDiLevel nCurrDir;
2701 
2702         for ( long nIdx = 0; nIdx < nCount; ++nIdx )
2703         {
2704             ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
2705             // bit 0 of nCurrDir indicates direction
2706             rInfos.emplace_back( /*bLeftToRight*/ nCurrDir % 2 == 0, nStart, nEnd );
2707             nStart = nEnd;
2708         }
2709 
2710         ubidi_close( pBidi );
2711     }
2712 
2713     // No infos mean no CTL and default dir is L2R...
2714     if ( rInfos.empty() )
2715         rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->GetText().getLength() );
2716 
2717 }
2718 
2719 bool TextEngine::ImpGetRightToLeft( sal_uInt32 nPara, sal_Int32 nPos )
2720 {
2721     bool bRightToLeft = false;
2722 
2723     TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2724     if ( pNode && !pNode->GetText().isEmpty() )
2725     {
2726         TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
2727         if ( pParaPortion->GetWritingDirectionInfos().empty() )
2728             ImpInitWritingDirections( nPara );
2729 
2730         std::vector<TEWritingDirectionInfo>& rDirInfos = pParaPortion->GetWritingDirectionInfos();
2731         for ( const auto& rWritingDirectionInfo : rDirInfos )
2732         {
2733             if ( rWritingDirectionInfo.nStartPos <= nPos && rWritingDirectionInfo.nEndPos >= nPos )
2734             {
2735                 bRightToLeft = !rWritingDirectionInfo.bLeftToRight;
2736                 break;
2737             }
2738         }
2739     }
2740     return bRightToLeft;
2741 }
2742 
2743 long TextEngine::ImpGetPortionXOffset( sal_uInt32 nPara, TextLine const * pLine, std::size_t nTextPortion )
2744 {
2745     long nX = pLine->GetStartX();
2746 
2747     TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
2748 
2749     for ( std::size_t i = pLine->GetStartPortion(); i < nTextPortion; i++ )
2750     {
2751         TETextPortion* pPortion = pParaPortion->GetTextPortions()[ i ];
2752         nX += pPortion->GetWidth();
2753     }
2754 
2755     TETextPortion* pDestPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
2756     if ( pDestPortion->GetKind() != PORTIONKIND_TAB )
2757     {
2758         if ( !IsRightToLeft() && pDestPortion->IsRightToLeft() )
2759         {
2760             // Portions behind must be added, visual before this portion
2761             std::size_t nTmpPortion = nTextPortion+1;
2762             while ( nTmpPortion <= pLine->GetEndPortion() )
2763             {
2764                 TETextPortion* pNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2765                 if ( pNextTextPortion->IsRightToLeft() && ( pNextTextPortion->GetKind() != PORTIONKIND_TAB ) )
2766                     nX += pNextTextPortion->GetWidth();
2767                 else
2768                     break;
2769                 nTmpPortion++;
2770             }
2771             // Portions before must be removed, visual behind this portion
2772             nTmpPortion = nTextPortion;
2773             while ( nTmpPortion > pLine->GetStartPortion() )
2774             {
2775                 --nTmpPortion;
2776                 TETextPortion* pPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2777                 if ( pPrevTextPortion->IsRightToLeft() && ( pPrevTextPortion->GetKind() != PORTIONKIND_TAB ) )
2778                     nX -= pPrevTextPortion->GetWidth();
2779                 else
2780                     break;
2781             }
2782         }
2783         else if ( IsRightToLeft() && !pDestPortion->IsRightToLeft() )
2784         {
2785             // Portions behind must be removed, visual behind this portion
2786             std::size_t nTmpPortion = nTextPortion+1;
2787             while ( nTmpPortion <= pLine->GetEndPortion() )
2788             {
2789                 TETextPortion* pNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2790                 if ( !pNextTextPortion->IsRightToLeft() && ( pNextTextPortion->GetKind() != PORTIONKIND_TAB ) )
2791                     nX += pNextTextPortion->GetWidth();
2792                 else
2793                     break;
2794                 nTmpPortion++;
2795             }
2796             // Portions before must be added, visual before this portion
2797             nTmpPortion = nTextPortion;
2798             while ( nTmpPortion > pLine->GetStartPortion() )
2799             {
2800                 --nTmpPortion;
2801                 TETextPortion* pPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2802                 if ( !pPrevTextPortion->IsRightToLeft() && ( pPrevTextPortion->GetKind() != PORTIONKIND_TAB ) )
2803                     nX -= pPrevTextPortion->GetWidth();
2804                 else
2805                     break;
2806             }
2807         }
2808     }
2809 
2810     return nX;
2811 }
2812 
2813 void TextEngine::ImpInitLayoutMode( OutputDevice* pOutDev )
2814 {
2815     ComplexTextLayoutFlags nLayoutMode = pOutDev->GetLayoutMode();
2816 
2817     nLayoutMode &= ~ComplexTextLayoutFlags(ComplexTextLayoutFlags::BiDiRtl | ComplexTextLayoutFlags::BiDiStrong );
2818 
2819     pOutDev->SetLayoutMode( nLayoutMode );
2820 }
2821 
2822 TxtAlign TextEngine::ImpGetAlign() const
2823 {
2824     TxtAlign eAlign = meAlign;
2825     if ( IsRightToLeft() )
2826     {
2827         if ( eAlign == TxtAlign::Left )
2828             eAlign = TxtAlign::Right;
2829         else if ( eAlign == TxtAlign::Right )
2830             eAlign = TxtAlign::Left;
2831     }
2832     return eAlign;
2833 }
2834 
2835 long TextEngine::ImpGetOutputOffset( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, sal_Int32 nIndex2 )
2836 {
2837     TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
2838 
2839     sal_Int32 nPortionStart {0};
2840     const std::size_t nPortion = pPortion->GetTextPortions().FindPortion( nIndex, nPortionStart, true );
2841 
2842     TETextPortion* pTextPortion = pPortion->GetTextPortions()[ nPortion ];
2843 
2844     long nX;
2845 
2846     if ( ( nIndex == nPortionStart ) && ( nIndex == nIndex2 )  )
2847     {
2848         // Output of full portion, so we need portion x offset.
2849         // Use ImpGetPortionXOffset, because GetXPos may deliver left or right position from portion, depending on R2L, L2R
2850         nX = ImpGetPortionXOffset( nPara, pLine, nPortion );
2851         if ( IsRightToLeft() )
2852         {
2853             nX = -nX -pTextPortion->GetWidth();
2854         }
2855     }
2856     else
2857     {
2858         nX = ImpGetXPos( nPara, pLine, nIndex, nIndex == nPortionStart );
2859         if ( nIndex2 != nIndex )
2860         {
2861             const long nX2 = ImpGetXPos( nPara, pLine, nIndex2 );
2862             if ( ( !IsRightToLeft() && ( nX2 < nX ) ) ||
2863                  ( IsRightToLeft() && ( nX2 > nX ) ) )
2864             {
2865                 nX = nX2;
2866             }
2867         }
2868         if ( IsRightToLeft() )
2869         {
2870             nX = -nX;
2871         }
2872     }
2873 
2874     return nX;
2875 }
2876 
2877 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2878