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