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