bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / edit / texteng.cxx
blob397a1de8a23a010f31e1efddb9fe2cdc0a7c1000
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <tools/stream.hxx>
22 #include <vcl/texteng.hxx>
23 #include <vcl/textview.hxx>
24 #include <vcl/commandevent.hxx>
25 #include <vcl/inputctx.hxx>
26 #include "textdoc.hxx"
27 #include "textdat2.hxx"
28 #include "textundo.hxx"
29 #include "textund2.hxx"
30 #include <svl/ctloptions.hxx>
31 #include <vcl/window.hxx>
32 #include <vcl/settings.hxx>
33 #include <vcl/edit.hxx>
34 #include <vcl/virdev.hxx>
35 #include <sal/log.hxx>
36 #include <osl/diagnose.h>
38 #include <com/sun/star/i18n/XBreakIterator.hpp>
40 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
42 #include <com/sun/star/i18n/WordType.hpp>
44 #include <com/sun/star/i18n/InputSequenceChecker.hpp>
45 #include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
46 #include <com/sun/star/i18n/ScriptType.hpp>
48 #include <comphelper/processfactory.hxx>
50 #include <unotools/localedatawrapper.hxx>
51 #include <vcl/unohelp.hxx>
53 #include <vcl/svapp.hxx>
55 #include <unicode/ubidi.h>
57 #include <algorithm>
58 #include <cstddef>
59 #include <cstdlib>
60 #include <memory>
61 #include <set>
62 #include <string_view>
63 #include <vector>
65 using namespace ::com::sun::star;
66 using namespace ::com::sun::star::uno;
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}
88 mpViews.reset( new TextViews );
90 mpIdleFormatter.reset( new IdleFormatter );
91 mpIdleFormatter->SetInvokeHandler( LINK( this, TextEngine, IdleFormatHdl ) );
92 mpIdleFormatter->SetDebugName( "vcl::TextEngine mpIdleFormatter" );
94 mpRefDev = VclPtr<VirtualDevice>::Create();
96 ImpInitLayoutMode( mpRefDev );
98 ImpInitDoc();
100 vcl::Font aFont;
101 aFont.SetTransparent( false );
102 Color aFillColor( aFont.GetFillColor() );
103 aFillColor.SetTransparency( 0 );
104 aFont.SetFillColor( aFillColor );
105 SetFont( aFont );
108 TextEngine::~TextEngine()
110 mbDowning = true;
112 mpIdleFormatter.reset();
113 mpDoc.reset();
114 mpTEParaPortions.reset();
115 mpViews.reset(); // only the list, not the Views
116 mpRefDev.disposeAndClear();
117 mpUndoManager.reset();
118 mpIMEInfos.reset();
119 mpLocaleDataWrapper.reset();
122 void TextEngine::InsertView( TextView* pTextView )
124 mpViews->push_back( pTextView );
125 pTextView->SetSelection( TextSelection() );
127 if ( !GetActiveView() )
128 SetActiveView( pTextView );
131 void TextEngine::RemoveView( TextView* pTextView )
133 TextViews::iterator it = std::find( mpViews->begin(), mpViews->end(), pTextView );
134 if( it != mpViews->end() )
136 pTextView->HideCursor();
137 mpViews->erase( it );
138 if ( pTextView == GetActiveView() )
139 SetActiveView( nullptr );
143 sal_uInt16 TextEngine::GetViewCount() const
145 return mpViews->size();
148 TextView* TextEngine::GetView( sal_uInt16 nView ) const
150 return (*mpViews)[ nView ];
154 void TextEngine::SetActiveView( TextView* pTextView )
156 if ( pTextView != mpActiveView )
158 if ( mpActiveView )
159 mpActiveView->HideSelection();
161 mpActiveView = pTextView;
163 if ( mpActiveView )
164 mpActiveView->ShowSelection();
168 void TextEngine::SetFont( const vcl::Font& rFont )
170 if ( rFont == maFont )
171 return;
173 maFont = rFont;
174 // #i40221# As the font's color now defaults to transparent (since i35764)
175 // we have to choose a useful textcolor in this case.
176 // Otherwise maTextColor and maFont.GetColor() are both transparent...
177 if( rFont.GetColor() == COL_TRANSPARENT )
178 maTextColor = COL_BLACK;
179 else
180 maTextColor = rFont.GetColor();
182 // Do not allow transparent fonts because of selection
183 // (otherwise delete the background in ImplPaint later differently)
184 maFont.SetTransparent( false );
185 // Tell VCL not to use the font color, use text color from OutputDevice
186 maFont.SetColor( COL_TRANSPARENT );
187 Color aFillColor( maFont.GetFillColor() );
188 aFillColor.SetTransparency( 0 );
189 maFont.SetFillColor( aFillColor );
191 maFont.SetAlignment( ALIGN_TOP );
192 mpRefDev->SetFont( maFont );
193 mnDefTab = mpRefDev->GetTextWidth(" ");
194 if ( !mnDefTab )
195 mnDefTab = mpRefDev->GetTextWidth("XXXX");
196 if ( !mnDefTab )
197 mnDefTab = 1;
198 mnCharHeight = mpRefDev->GetTextHeight();
200 FormatFullDoc();
201 UpdateViews();
203 for ( auto nView = mpViews->size(); nView; )
205 TextView* pView = (*mpViews)[ --nView ];
206 pView->GetWindow()->SetInputContext( InputContext( GetFont(), !pView->IsReadOnly() ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
211 void TextEngine::SetMaxTextLen( sal_Int32 nLen )
213 mnMaxTextLen = nLen>=0 ? nLen : EDIT_NOLIMIT;
216 void TextEngine::SetMaxTextWidth( long nMaxWidth )
218 if ( nMaxWidth>=0 && nMaxWidth != mnMaxTextWidth )
220 mnMaxTextWidth = nMaxWidth;
221 FormatFullDoc();
222 UpdateViews();
226 static const sal_Unicode static_aLFText[] = { '\n', 0 };
227 static const sal_Unicode static_aCRText[] = { '\r', 0 };
228 static const sal_Unicode static_aCRLFText[] = { '\r', '\n', 0 };
230 static const sal_Unicode* static_getLineEndText( LineEnd aLineEnd )
232 const sal_Unicode* pRet = nullptr;
234 switch( aLineEnd )
236 case LINEEND_LF:
237 pRet = static_aLFText;
238 break;
239 case LINEEND_CR:
240 pRet = static_aCRText;
241 break;
242 case LINEEND_CRLF:
243 pRet = static_aCRLFText;
244 break;
246 return pRet;
249 void TextEngine::ReplaceText(const TextSelection& rSel, const OUString& rText)
251 ImpInsertText( rSel, rText );
254 OUString TextEngine::GetText( LineEnd aSeparator ) const
256 return mpDoc->GetText( static_getLineEndText( aSeparator ) );
259 OUString TextEngine::GetTextLines( LineEnd aSeparator ) const
261 OUStringBuffer aText;
262 const sal_uInt32 nParas = mpTEParaPortions->Count();
263 const sal_Unicode* pSep = static_getLineEndText( aSeparator );
264 for ( sal_uInt32 nP = 0; nP < nParas; ++nP )
266 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nP );
268 const size_t nLines = pTEParaPortion->GetLines().size();
269 for ( size_t nL = 0; nL < nLines; ++nL )
271 TextLine& rLine = pTEParaPortion->GetLines()[nL];
272 aText.append( std::u16string_view(pTEParaPortion->GetNode()->GetText()).substr(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()) );
273 if ( pSep && ( ( (nP+1) < nParas ) || ( (nL+1) < nLines ) ) )
274 aText.append(pSep);
277 return aText.makeStringAndClear();
280 OUString TextEngine::GetText( sal_uInt32 nPara ) const
282 return mpDoc->GetText( nPara );
285 sal_Int32 TextEngine::GetTextLen() const
287 return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ) );
290 sal_Int32 TextEngine::GetTextLen( const TextSelection& rSel ) const
292 TextSelection aSel( rSel );
293 aSel.Justify();
294 ValidateSelection( aSel );
295 return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ), &aSel );
298 sal_Int32 TextEngine::GetTextLen( const sal_uInt32 nPara ) const
300 return mpDoc->GetNodes()[ nPara ]->GetText().getLength();
303 void TextEngine::SetUpdateMode( bool bUpdate )
305 if ( bUpdate != mbUpdate )
307 mbUpdate = bUpdate;
308 if ( mbUpdate )
310 FormatAndUpdate( GetActiveView() );
311 if ( GetActiveView() )
312 GetActiveView()->ShowCursor();
317 bool TextEngine::DoesKeyChangeText( const KeyEvent& rKeyEvent )
319 bool bDoesChange = false;
321 KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
322 if ( eFunc != KeyFuncType::DONTKNOW )
324 switch ( eFunc )
326 case KeyFuncType::UNDO:
327 case KeyFuncType::REDO:
328 case KeyFuncType::CUT:
329 case KeyFuncType::PASTE:
330 bDoesChange = true;
331 break;
332 default:
333 // might get handled below
334 eFunc = KeyFuncType::DONTKNOW;
337 if ( eFunc == KeyFuncType::DONTKNOW )
339 switch ( rKeyEvent.GetKeyCode().GetCode() )
341 case KEY_DELETE:
342 case KEY_BACKSPACE:
343 if ( !rKeyEvent.GetKeyCode().IsMod2() )
344 bDoesChange = true;
345 break;
346 case KEY_RETURN:
347 case KEY_TAB:
348 if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() )
349 bDoesChange = true;
350 break;
351 default:
352 bDoesChange = TextEngine::IsSimpleCharInput( rKeyEvent );
355 return bDoesChange;
358 bool TextEngine::IsSimpleCharInput( const KeyEvent& rKeyEvent )
360 return rKeyEvent.GetCharCode() >= 32 && rKeyEvent.GetCharCode() != 127 &&
361 KEY_MOD1 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT) && // (ssa) #i45714#:
362 KEY_MOD2 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT); // check for Ctrl and Alt separately
365 void TextEngine::ImpInitDoc()
367 if ( mpDoc )
368 mpDoc->Clear();
369 else
370 mpDoc.reset( new TextDoc );
372 mpTEParaPortions.reset(new TEParaPortions);
374 std::unique_ptr<TextNode> pNode(new TextNode( OUString() ));
375 mpDoc->GetNodes().insert( mpDoc->GetNodes().begin(), std::move(pNode) );
377 TEParaPortion* pIniPortion = new TEParaPortion( mpDoc->GetNodes().begin()->get() );
378 mpTEParaPortions->Insert( pIniPortion, 0 );
380 mbFormatted = false;
382 ImpParagraphRemoved( TEXT_PARA_ALL );
383 ImpParagraphInserted( 0 );
386 OUString TextEngine::GetText( const TextSelection& rSel, LineEnd aSeparator ) const
388 if ( !rSel.HasRange() )
389 return OUString();
391 TextSelection aSel( rSel );
392 aSel.Justify();
394 OUStringBuffer aText;
395 const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
396 const sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
397 const sal_Unicode* pSep = static_getLineEndText( aSeparator );
398 for ( sal_uInt32 nNode = aSel.GetStart().GetPara(); nNode <= nEndPara; ++nNode )
400 TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
402 sal_Int32 nStartPos = 0;
403 sal_Int32 nEndPos = pNode->GetText().getLength();
404 if ( nNode == nStartPara )
405 nStartPos = aSel.GetStart().GetIndex();
406 if ( nNode == nEndPara ) // may also be == nStart!
407 nEndPos = aSel.GetEnd().GetIndex();
409 aText.append(std::u16string_view(pNode->GetText()).substr(nStartPos, nEndPos-nStartPos));
410 if ( nNode < nEndPara )
411 aText.append(pSep);
413 return aText.makeStringAndClear();
416 void TextEngine::ImpRemoveText()
418 ImpInitDoc();
420 const TextSelection aEmptySel;
421 for (TextView* pView : *mpViews)
423 pView->ImpSetSelection( aEmptySel );
425 ResetUndo();
428 void TextEngine::SetText( const OUString& rText )
430 ImpRemoveText();
432 const bool bUndoCurrentlyEnabled = IsUndoEnabled();
433 // the manually inserted text cannot be reversed by the user
434 EnableUndo( false );
436 const TextSelection aEmptySel;
438 TextPaM aPaM;
439 if ( !rText.isEmpty() )
440 aPaM = ImpInsertText( aEmptySel, rText );
442 for (TextView* pView : *mpViews)
444 pView->ImpSetSelection( aEmptySel );
446 // if no text, then no Format&Update => the text remains
447 if ( rText.isEmpty() && GetUpdateMode() )
448 pView->Invalidate();
451 if( rText.isEmpty() ) // otherwise needs invalidation later; !bFormatted is sufficient
452 mnCurTextHeight = 0;
454 FormatAndUpdate();
456 EnableUndo( bUndoCurrentlyEnabled );
457 SAL_WARN_IF( HasUndoManager() && GetUndoManager().GetUndoActionCount(), "vcl", "SetText: Undo!" );
460 void TextEngine::CursorMoved( sal_uInt32 nNode )
462 // delete empty attribute; but only if paragraph is not empty!
463 TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
464 if ( pNode && pNode->GetCharAttribs().HasEmptyAttribs() && !pNode->GetText().isEmpty() )
465 pNode->GetCharAttribs().DeleteEmptyAttribs();
468 void TextEngine::ImpRemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
470 SAL_WARN_IF( !nChars, "vcl", "ImpRemoveChars: 0 Chars?!" );
471 if ( IsUndoEnabled() && !IsInUndo() )
473 // attributes have to be saved for UNDO before RemoveChars!
474 TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
475 OUString aStr( pNode->GetText().copy( rPaM.GetIndex(), nChars ) );
477 // check if attributes are being deleted or changed
478 const sal_Int32 nStart = rPaM.GetIndex();
479 const sal_Int32 nEnd = nStart + nChars;
480 for ( sal_uInt16 nAttr = pNode->GetCharAttribs().Count(); nAttr; )
482 TextCharAttrib& rAttr = pNode->GetCharAttribs().GetAttrib( --nAttr );
483 if ( ( rAttr.GetEnd() >= nStart ) && ( rAttr.GetStart() < nEnd ) )
485 break; // for
488 InsertUndo( std::make_unique<TextUndoRemoveChars>( this, rPaM, aStr ) );
491 mpDoc->RemoveChars( rPaM, nChars );
492 ImpCharsRemoved( rPaM.GetPara(), rPaM.GetIndex(), nChars );
495 TextPaM TextEngine::ImpConnectParagraphs( sal_uInt32 nLeft, sal_uInt32 nRight )
497 SAL_WARN_IF( nLeft == nRight, "vcl", "ImpConnectParagraphs: connect the very same paragraph ?" );
499 TextNode* pLeft = mpDoc->GetNodes()[ nLeft ].get();
500 TextNode* pRight = mpDoc->GetNodes()[ nRight ].get();
502 if ( IsUndoEnabled() && !IsInUndo() )
503 InsertUndo( std::make_unique<TextUndoConnectParas>( this, nLeft, pLeft->GetText().getLength() ) );
505 // first lookup Portions, as pRight is gone after ConnectParagraphs
506 TEParaPortion* pLeftPortion = mpTEParaPortions->GetObject( nLeft );
507 TEParaPortion* pRightPortion = mpTEParaPortions->GetObject( nRight );
508 SAL_WARN_IF( !pLeft || !pLeftPortion, "vcl", "ImpConnectParagraphs(1): Hidden Portion" );
509 SAL_WARN_IF( !pRight || !pRightPortion, "vcl", "ImpConnectParagraphs(2): Hidden Portion" );
511 TextPaM aPaM = mpDoc->ConnectParagraphs( pLeft, pRight );
512 ImpParagraphRemoved( nRight );
514 pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() );
516 mpTEParaPortions->Remove( nRight );
517 // the right Node is deleted by EditDoc::ConnectParagraphs()
519 return aPaM;
522 TextPaM TextEngine::ImpDeleteText( const TextSelection& rSel )
524 if ( !rSel.HasRange() )
525 return rSel.GetStart();
527 TextSelection aSel( rSel );
528 aSel.Justify();
529 TextPaM aStartPaM( aSel.GetStart() );
530 TextPaM aEndPaM( aSel.GetEnd() );
532 CursorMoved( aStartPaM.GetPara() ); // so that newly-adjusted attributes vanish
533 CursorMoved( aEndPaM.GetPara() ); // so that newly-adjusted attributes vanish
535 SAL_WARN_IF( !mpDoc->IsValidPaM( aStartPaM ), "vcl", "ImpDeleteText(1): bad Index" );
536 SAL_WARN_IF( !mpDoc->IsValidPaM( aEndPaM ), "vcl", "ImpDeleteText(2): bad Index" );
538 const sal_uInt32 nStartNode = aStartPaM.GetPara();
539 sal_uInt32 nEndNode = aEndPaM.GetPara();
541 // remove all Nodes inbetween
542 for ( sal_uInt32 z = nStartNode+1; z < nEndNode; ++z )
544 // always nStartNode+1, because of Remove()!
545 ImpRemoveParagraph( nStartNode+1 );
548 if ( nStartNode != nEndNode )
550 // the remainder of StartNodes...
551 TextNode* pLeft = mpDoc->GetNodes()[ nStartNode ].get();
552 sal_Int32 nChars = pLeft->GetText().getLength() - aStartPaM.GetIndex();
553 if ( nChars )
555 ImpRemoveChars( aStartPaM, nChars );
556 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
557 SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(3): bad Index" );
558 pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() );
561 // the beginning of EndNodes...
562 nEndNode = nStartNode+1; // the other paragraphs were deleted
563 nChars = aEndPaM.GetIndex();
564 if ( nChars )
566 aEndPaM.GetPara() = nEndNode;
567 aEndPaM.GetIndex() = 0;
568 ImpRemoveChars( aEndPaM, nChars );
569 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nEndNode );
570 SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(4): bad Index" );
571 pPortion->MarkSelectionInvalid( 0 );
574 // connect...
575 aStartPaM = ImpConnectParagraphs( nStartNode, nEndNode );
577 else
579 const sal_Int32 nChars = aEndPaM.GetIndex() - aStartPaM.GetIndex();
580 ImpRemoveChars( aStartPaM, nChars );
581 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
582 SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(5): bad Index" );
583 pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() );
586 // UpdateSelections();
587 TextModified();
588 return aStartPaM;
591 void TextEngine::ImpRemoveParagraph( sal_uInt32 nPara )
593 std::unique_ptr<TextNode> pNode = std::move(mpDoc->GetNodes()[ nPara ]);
595 // the Node is handled by Undo and is deleted if appropriate
596 mpDoc->GetNodes().erase( mpDoc->GetNodes().begin() + nPara );
597 if ( IsUndoEnabled() && !IsInUndo() )
598 InsertUndo( std::make_unique<TextUndoDelPara>( this, pNode.release(), nPara ) );
600 mpTEParaPortions->Remove( nPara );
602 ImpParagraphRemoved( nPara );
605 uno::Reference < i18n::XExtendedInputSequenceChecker > const & TextEngine::GetInputSequenceChecker()
607 if ( !mxISC.is() )
609 mxISC = i18n::InputSequenceChecker::create( ::comphelper::getProcessComponentContext() );
611 return mxISC;
614 bool TextEngine::IsInputSequenceCheckingRequired( sal_Unicode c, const TextSelection& rCurSel ) const
616 SvtCTLOptions aCTLOptions;
618 // get the index that really is first
619 const sal_Int32 nFirstPos = std::min(rCurSel.GetStart().GetIndex(), rCurSel.GetEnd().GetIndex());
621 bool bIsSequenceChecking =
622 aCTLOptions.IsCTLFontEnabled() &&
623 aCTLOptions.IsCTLSequenceChecking() &&
624 nFirstPos != 0; /* first char needs not to be checked */
626 if (bIsSequenceChecking)
628 uno::Reference< i18n::XBreakIterator > xBI = const_cast<TextEngine *>(this)->GetBreakIterator();
629 bIsSequenceChecking = xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( OUString( c ), 0 );
632 return bIsSequenceChecking;
635 TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, sal_Unicode c, bool bOverwrite )
637 return ImpInsertText( c, rCurSel, bOverwrite );
640 TextPaM TextEngine::ImpInsertText( sal_Unicode c, const TextSelection& rCurSel, bool bOverwrite, bool bIsUserInput )
642 SAL_WARN_IF( c == '\n', "vcl", "InsertText: NewLine!" );
643 SAL_WARN_IF( c == '\r', "vcl", "InsertText: NewLine!" );
645 TextPaM aPaM( rCurSel.GetStart() );
646 TextNode* pNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
648 bool bDoOverwrite = bOverwrite && ( aPaM.GetIndex() < pNode->GetText().getLength() );
650 bool bUndoAction = rCurSel.HasRange() || bDoOverwrite;
652 if ( bUndoAction )
653 UndoActionStart();
655 if ( rCurSel.HasRange() )
657 aPaM = ImpDeleteText( rCurSel );
659 else if ( bDoOverwrite )
661 // if selection, then don't overwrite a character
662 TextSelection aTmpSel( aPaM );
663 ++aTmpSel.GetEnd().GetIndex();
664 ImpDeleteText( aTmpSel );
667 if (bIsUserInput && IsInputSequenceCheckingRequired( c, rCurSel ))
669 uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = GetInputSequenceChecker();
670 SvtCTLOptions aCTLOptions;
672 if (xISC.is())
674 sal_Int32 nTmpPos = aPaM.GetIndex();
675 sal_Int16 nCheckMode = aCTLOptions.IsCTLSequenceCheckingRestricted() ?
676 i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
678 // the text that needs to be checked is only the one
679 // before the current cursor position
680 OUString aOldText( mpDoc->GetText( aPaM.GetPara() ).copy(0, nTmpPos) );
681 if (aCTLOptions.IsCTLSequenceCheckingTypeAndReplace())
683 OUString aNewText( aOldText );
684 xISC->correctInputSequence( aNewText, nTmpPos - 1, c, nCheckMode );
686 // find position of first character that has changed
687 const sal_Int32 nOldLen = aOldText.getLength();
688 const sal_Int32 nNewLen = aNewText.getLength();
689 const sal_Unicode *pOldTxt = aOldText.getStr();
690 const sal_Unicode *pNewTxt = aNewText.getStr();
691 sal_Int32 nChgPos = 0;
692 while ( nChgPos < nOldLen && nChgPos < nNewLen &&
693 pOldTxt[nChgPos] == pNewTxt[nChgPos] )
694 ++nChgPos;
696 OUString aChgText( aNewText.copy( nChgPos ) );
698 // select text from first pos to be changed to current pos
699 TextSelection aSel( TextPaM( aPaM.GetPara(), nChgPos ), aPaM );
701 if (!aChgText.isEmpty())
702 // ImpInsertText implicitly handles undo...
703 return ImpInsertText( aSel, aChgText );
704 else
705 return aPaM;
707 else
709 // should the character be ignored (i.e. not get inserted) ?
710 if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode ))
711 return aPaM; // nothing to be done -> no need for undo
715 // at this point now we will insert the character 'normally' some lines below...
718 if ( IsUndoEnabled() && !IsInUndo() )
720 std::unique_ptr<TextUndoInsertChars> pNewUndo(new TextUndoInsertChars( this, aPaM, OUString(c) ));
721 bool bTryMerge = !bDoOverwrite && ( c != ' ' );
722 InsertUndo( std::move(pNewUndo), bTryMerge );
725 TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
726 pPortion->MarkInvalid( aPaM.GetIndex(), 1 );
727 if ( c == '\t' )
728 pPortion->SetNotSimpleInvalid();
729 aPaM = mpDoc->InsertText( aPaM, c );
730 ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-1, 1 );
732 TextModified();
734 if ( bUndoAction )
735 UndoActionEnd();
737 return aPaM;
740 TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, const OUString& rStr )
742 UndoActionStart();
744 TextPaM aPaM;
746 if ( rCurSel.HasRange() )
747 aPaM = ImpDeleteText( rCurSel );
748 else
749 aPaM = rCurSel.GetEnd();
751 OUString aText(convertLineEnd(rStr, LINEEND_LF));
753 sal_Int32 nStart = 0;
754 while ( nStart < aText.getLength() )
756 sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart );
757 if (nEnd == -1)
758 nEnd = aText.getLength(); // do not dereference!
760 // Start == End => empty line
761 if ( nEnd > nStart )
763 OUString aLine(aText.copy(nStart, nEnd-nStart));
764 if ( IsUndoEnabled() && !IsInUndo() )
765 InsertUndo( std::make_unique<TextUndoInsertChars>( this, aPaM, aLine ) );
767 TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
768 pPortion->MarkInvalid( aPaM.GetIndex(), aLine.getLength() );
769 if (aLine.indexOf( '\t' ) != -1)
770 pPortion->SetNotSimpleInvalid();
772 aPaM = mpDoc->InsertText( aPaM, aLine );
773 ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-aLine.getLength(), aLine.getLength() );
776 if ( nEnd < aText.getLength() )
777 aPaM = ImpInsertParaBreak( aPaM );
779 if ( nEnd == aText.getLength() ) // #108611# prevent overflow in "nStart = nEnd+1" calculation
780 break;
782 nStart = nEnd+1;
785 UndoActionEnd();
787 TextModified();
788 return aPaM;
791 TextPaM TextEngine::ImpInsertParaBreak( const TextSelection& rCurSel )
793 TextPaM aPaM;
794 if ( rCurSel.HasRange() )
795 aPaM = ImpDeleteText( rCurSel );
796 else
797 aPaM = rCurSel.GetEnd();
799 return ImpInsertParaBreak( aPaM );
802 TextPaM TextEngine::ImpInsertParaBreak( const TextPaM& rPaM )
804 if ( IsUndoEnabled() && !IsInUndo() )
805 InsertUndo( std::make_unique<TextUndoSplitPara>( this, rPaM.GetPara(), rPaM.GetIndex() ) );
807 TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
808 bool bFirstParaContentChanged = rPaM.GetIndex() < pNode->GetText().getLength();
810 TextPaM aPaM( mpDoc->InsertParaBreak( rPaM ) );
812 TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
813 SAL_WARN_IF( !pPortion, "vcl", "ImpInsertParaBreak: Hidden Portion" );
814 pPortion->MarkInvalid( rPaM.GetIndex(), 0 );
816 TextNode* pNewNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
817 TEParaPortion* pNewPortion = new TEParaPortion( pNewNode );
818 mpTEParaPortions->Insert( pNewPortion, aPaM.GetPara() );
819 ImpParagraphInserted( aPaM.GetPara() );
821 CursorMoved( rPaM.GetPara() ); // if empty attribute created
822 TextModified();
824 if ( bFirstParaContentChanged )
825 Broadcast( TextHint( SfxHintId::TextParaContentChanged, rPaM.GetPara() ) );
827 return aPaM;
830 tools::Rectangle TextEngine::PaMtoEditCursor( const TextPaM& rPaM, bool bSpecial )
832 SAL_WARN_IF( !GetUpdateMode(), "vcl", "PaMtoEditCursor: GetUpdateMode()" );
834 tools::Rectangle aEditCursor;
835 long nY = 0;
837 if ( !mbHasMultiLineParas )
839 nY = rPaM.GetPara() * mnCharHeight;
841 else
843 for ( sal_uInt32 nPortion = 0; nPortion < rPaM.GetPara(); ++nPortion )
845 TEParaPortion* pPortion = mpTEParaPortions->GetObject(nPortion);
846 nY += pPortion->GetLines().size() * mnCharHeight;
850 aEditCursor = GetEditCursor( rPaM, bSpecial );
851 aEditCursor.AdjustTop(nY );
852 aEditCursor.AdjustBottom(nY );
853 return aEditCursor;
856 tools::Rectangle TextEngine::GetEditCursor( const TextPaM& rPaM, bool bSpecial, bool bPreferPortionStart )
858 if ( !IsFormatted() && !IsFormatting() )
859 FormatAndUpdate();
861 TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
862 //TextNode* pNode = mpDoc->GetNodes().GetObject( rPaM.GetPara() );
865 bSpecial: If behind the last character of a made up line, stay at the
866 end of the line, not at the start of the next line.
867 Purpose: - really END = > behind the last character
868 - to selection...
872 long nY = 0;
873 sal_Int32 nCurIndex = 0;
874 TextLine* pLine = nullptr;
875 for (TextLine & rTmpLine : pPortion->GetLines())
877 if ( ( rTmpLine.GetStart() == rPaM.GetIndex() ) || ( rTmpLine.IsIn( rPaM.GetIndex(), bSpecial ) ) )
879 pLine = &rTmpLine;
880 break;
883 nCurIndex = nCurIndex + rTmpLine.GetLen();
884 nY += mnCharHeight;
886 if ( !pLine )
888 // Cursor at end of paragraph
889 SAL_WARN_IF( rPaM.GetIndex() != nCurIndex, "vcl", "GetEditCursor: Bad Index!" );
891 pLine = & ( pPortion->GetLines().back() );
892 nY -= mnCharHeight;
895 tools::Rectangle aEditCursor;
897 aEditCursor.SetTop( nY );
898 nY += mnCharHeight;
899 aEditCursor.SetBottom( nY-1 );
901 // search within the line
902 long nX = ImpGetXPos( rPaM.GetPara(), pLine, rPaM.GetIndex(), bPreferPortionStart );
903 aEditCursor.SetLeft(nX);
904 aEditCursor.SetRight(nX);
905 return aEditCursor;
908 long TextEngine::ImpGetXPos( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart )
910 SAL_WARN_IF( ( nIndex < pLine->GetStart() ) || ( nIndex > pLine->GetEnd() ) , "vcl", "ImpGetXPos: Bad parameters!" );
912 bool bDoPreferPortionStart = bPreferPortionStart;
913 // Assure that the portion belongs to this line
914 if ( nIndex == pLine->GetStart() )
915 bDoPreferPortionStart = true;
916 else if ( nIndex == pLine->GetEnd() )
917 bDoPreferPortionStart = false;
919 TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
921 sal_Int32 nTextPortionStart = 0;
922 std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart );
924 SAL_WARN_IF( ( nTextPortion < pLine->GetStartPortion() ) || ( nTextPortion > pLine->GetEndPortion() ), "vcl", "GetXPos: Portion not in current line!" );
926 TETextPortion* pPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
928 long nX = ImpGetPortionXOffset( nPara, pLine, nTextPortion );
930 long nPortionTextWidth = pPortion->GetWidth();
932 if ( nTextPortionStart != nIndex )
934 // Search within portion...
935 if ( nIndex == ( nTextPortionStart + pPortion->GetLen() ) )
937 // End of Portion
938 if ( ( pPortion->GetKind() == PORTIONKIND_TAB ) ||
939 ( !IsRightToLeft() && !pPortion->IsRightToLeft() ) ||
940 ( IsRightToLeft() && pPortion->IsRightToLeft() ) )
942 nX += nPortionTextWidth;
943 if ( ( pPortion->GetKind() == PORTIONKIND_TAB ) && ( (nTextPortion+1) < pParaPortion->GetTextPortions().size() ) )
945 TETextPortion* pNextPortion = pParaPortion->GetTextPortions()[ nTextPortion+1 ];
946 if (pNextPortion->GetKind() != PORTIONKIND_TAB && IsRightToLeft() != pNextPortion->IsRightToLeft())
948 // End of the tab portion, use start of next for cursor pos
949 SAL_WARN_IF( bPreferPortionStart, "vcl", "ImpGetXPos: How can we get here!" );
950 nX = ImpGetXPos( nPara, pLine, nIndex, true );
956 else if ( pPortion->GetKind() == PORTIONKIND_TEXT )
958 SAL_WARN_IF( nIndex == pLine->GetStart(), "vcl", "ImpGetXPos: Strange behavior" );
960 long nPosInPortion = CalcTextWidth( nPara, nTextPortionStart, nIndex-nTextPortionStart );
962 if (IsRightToLeft() == pPortion->IsRightToLeft())
964 nX += nPosInPortion;
966 else
968 nX += nPortionTextWidth - nPosInPortion;
972 else // if ( nIndex == pLine->GetStart() )
974 if (pPortion->GetKind() != PORTIONKIND_TAB && IsRightToLeft() != pPortion->IsRightToLeft())
976 nX += nPortionTextWidth;
980 return nX;
983 const TextAttrib* TextEngine::FindAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
985 const TextAttrib* pAttr = nullptr;
986 const TextCharAttrib* pCharAttr = FindCharAttrib( rPaM, nWhich );
987 if ( pCharAttr )
988 pAttr = &pCharAttr->GetAttr();
989 return pAttr;
992 const TextCharAttrib* TextEngine::FindCharAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
994 const TextCharAttrib* pAttr = nullptr;
995 TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
996 if (pNode && (rPaM.GetIndex() <= pNode->GetText().getLength()))
997 pAttr = pNode->GetCharAttribs().FindAttrib( nWhich, rPaM.GetIndex() );
998 return pAttr;
1001 TextPaM TextEngine::GetPaM( const Point& rDocPos )
1003 SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetPaM: GetUpdateMode()" );
1005 long nY = 0;
1006 for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
1008 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
1009 long nTmpHeight = pPortion->GetLines().size() * mnCharHeight;
1010 nY += nTmpHeight;
1011 if ( nY > rDocPos.Y() )
1013 nY -= nTmpHeight;
1014 Point aPosInPara( rDocPos );
1015 aPosInPara.AdjustY( -nY );
1017 TextPaM aPaM( nPortion, 0 );
1018 aPaM.GetIndex() = ImpFindIndex( nPortion, aPosInPara );
1019 return aPaM;
1023 // not found - go to last visible
1024 const sal_uInt32 nLastNode = static_cast<sal_uInt32>(mpDoc->GetNodes().size() - 1);
1025 TextNode* pLast = mpDoc->GetNodes()[ nLastNode ].get();
1026 return TextPaM( nLastNode, pLast->GetText().getLength() );
1029 sal_Int32 TextEngine::ImpFindIndex( sal_uInt32 nPortion, const Point& rPosInPara )
1031 SAL_WARN_IF( !IsFormatted(), "vcl", "GetPaM: Not formatted" );
1032 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
1034 sal_Int32 nCurIndex = 0;
1036 long nY = 0;
1037 TextLine* pLine = nullptr;
1038 std::vector<TextLine>::size_type nLine;
1039 for ( nLine = 0; nLine < pPortion->GetLines().size(); nLine++ )
1041 TextLine& rmpLine = pPortion->GetLines()[ nLine ];
1042 nY += mnCharHeight;
1043 if ( nY > rPosInPara.Y() ) // that's it
1045 pLine = &rmpLine;
1046 break; // correct Y-Position not needed
1050 assert(pLine && "ImpFindIndex: pLine ?");
1052 nCurIndex = GetCharPos( nPortion, nLine, rPosInPara.X() );
1054 if ( nCurIndex && ( nCurIndex == pLine->GetEnd() ) &&
1055 ( pLine != &( pPortion->GetLines().back() ) ) )
1057 uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
1058 sal_Int32 nCount = 1;
1059 nCurIndex = xBI->previousCharacters( pPortion->GetNode()->GetText(), nCurIndex, GetLocale(), i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
1061 return nCurIndex;
1064 sal_Int32 TextEngine::GetCharPos( sal_uInt32 nPortion, std::vector<TextLine>::size_type nLine, long nXPos )
1067 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
1068 TextLine& rLine = pPortion->GetLines()[ nLine ];
1070 sal_Int32 nCurIndex = rLine.GetStart();
1072 long nTmpX = rLine.GetStartX();
1073 if ( nXPos <= nTmpX )
1074 return nCurIndex;
1076 for ( std::size_t i = rLine.GetStartPortion(); i <= rLine.GetEndPortion(); i++ )
1078 TETextPortion* pTextPortion = pPortion->GetTextPortions()[ i ];
1079 nTmpX += pTextPortion->GetWidth();
1081 if ( nTmpX > nXPos )
1083 if( pTextPortion->GetLen() > 1 )
1085 nTmpX -= pTextPortion->GetWidth(); // position before Portion
1086 // TODO: Optimize: no GetTextBreak if fixed-width Font
1087 vcl::Font aFont;
1088 SeekCursor( nPortion, nCurIndex+1, aFont, nullptr );
1089 mpRefDev->SetFont( aFont);
1090 long nPosInPortion = nXPos-nTmpX;
1091 if ( IsRightToLeft() != pTextPortion->IsRightToLeft() )
1092 nPosInPortion = pTextPortion->GetWidth() - nPosInPortion;
1093 nCurIndex = mpRefDev->GetTextBreak( pPortion->GetNode()->GetText(), nPosInPortion, nCurIndex );
1094 // MT: GetTextBreak should assure that we are not within a CTL cell...
1096 return nCurIndex;
1098 nCurIndex += pTextPortion->GetLen();
1100 return nCurIndex;
1103 long TextEngine::GetTextHeight() const
1105 SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
1107 if ( !IsFormatted() && !IsFormatting() )
1108 const_cast<TextEngine*>(this)->FormatAndUpdate();
1110 return mnCurTextHeight;
1113 long TextEngine::GetTextHeight( sal_uInt32 nParagraph ) const
1115 SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
1117 if ( !IsFormatted() && !IsFormatting() )
1118 const_cast<TextEngine*>(this)->FormatAndUpdate();
1120 return CalcParaHeight( nParagraph );
1123 long TextEngine::CalcTextWidth( sal_uInt32 nPara )
1125 long nParaWidth = 0;
1126 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
1127 for ( auto nLine = pPortion->GetLines().size(); nLine; )
1129 long nLineWidth = 0;
1130 TextLine& rLine = pPortion->GetLines()[ --nLine ];
1131 for ( std::size_t nTP = rLine.GetStartPortion(); nTP <= rLine.GetEndPortion(); nTP++ )
1133 TETextPortion* pTextPortion = pPortion->GetTextPortions()[ nTP ];
1134 nLineWidth += pTextPortion->GetWidth();
1136 if ( nLineWidth > nParaWidth )
1137 nParaWidth = nLineWidth;
1139 return nParaWidth;
1142 long TextEngine::CalcTextWidth()
1144 if ( !IsFormatted() && !IsFormatting() )
1145 FormatAndUpdate();
1147 if ( mnCurTextWidth < 0 )
1149 mnCurTextWidth = 0;
1150 for ( sal_uInt32 nPara = mpTEParaPortions->Count(); nPara; )
1152 const long nParaWidth = CalcTextWidth( --nPara );
1153 if ( nParaWidth > mnCurTextWidth )
1154 mnCurTextWidth = nParaWidth;
1157 return mnCurTextWidth+1;// wider by 1, as CreateLines breaks at >=
1160 long TextEngine::CalcTextHeight()
1162 SAL_WARN_IF( !GetUpdateMode(), "vcl", "CalcTextHeight: GetUpdateMode()" );
1164 long nY = 0;
1165 for ( auto nPortion = mpTEParaPortions->Count(); nPortion; )
1166 nY += CalcParaHeight( --nPortion );
1167 return nY;
1170 long TextEngine::CalcTextWidth( sal_uInt32 nPara, sal_Int32 nPortionStart, sal_Int32 nLen )
1172 #ifdef DBG_UTIL
1173 // within the text there must not be a Portion change (attribute/tab)!
1174 sal_Int32 nTabPos = mpDoc->GetNodes()[ nPara ]->GetText().indexOf( '\t', nPortionStart );
1175 SAL_WARN_IF( nTabPos != -1 && nTabPos < (nPortionStart+nLen), "vcl", "CalcTextWidth: Tab!" );
1176 #endif
1178 vcl::Font aFont;
1179 SeekCursor( nPara, nPortionStart+1, aFont, nullptr );
1180 mpRefDev->SetFont( aFont );
1181 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1182 long nWidth = mpRefDev->GetTextWidth( pNode->GetText(), nPortionStart, nLen );
1183 return nWidth;
1186 void TextEngine::GetTextPortionRange(const TextPaM& rPaM, sal_Int32& nStart, sal_Int32& nEnd)
1188 nStart = 0;
1189 nEnd = 0;
1190 TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
1191 for ( std::size_t i = 0; i < pParaPortion->GetTextPortions().size(); ++i )
1193 TETextPortion* pTextPortion = pParaPortion->GetTextPortions()[ i ];
1194 if (nStart + pTextPortion->GetLen() > rPaM.GetIndex())
1196 nEnd = nStart + pTextPortion->GetLen();
1197 return;
1199 else
1201 nStart += pTextPortion->GetLen();
1206 sal_uInt16 TextEngine::GetLineCount( sal_uInt32 nParagraph ) const
1208 SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
1210 TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
1211 if ( pPPortion )
1212 return pPPortion->GetLines().size();
1214 return 0;
1217 sal_Int32 TextEngine::GetLineLen( sal_uInt32 nParagraph, sal_uInt16 nLine ) const
1219 SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
1221 TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
1222 if ( pPPortion && ( nLine < pPPortion->GetLines().size() ) )
1224 return pPPortion->GetLines()[ nLine ].GetLen();
1227 return 0;
1230 long TextEngine::CalcParaHeight( sal_uInt32 nParagraph ) const
1232 long nHeight = 0;
1234 TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
1235 SAL_WARN_IF( !pPPortion, "vcl", "GetParaHeight: paragraph not found" );
1236 if ( pPPortion )
1237 nHeight = pPPortion->GetLines().size() * mnCharHeight;
1239 return nHeight;
1242 Range TextEngine::GetInvalidYOffsets( sal_uInt32 nPortion )
1244 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
1245 sal_uInt16 nLines = pTEParaPortion->GetLines().size();
1246 sal_uInt16 nLastInvalid, nFirstInvalid = 0;
1247 sal_uInt16 nLine;
1248 for ( nLine = 0; nLine < nLines; nLine++ )
1250 TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
1251 if ( rL.IsInvalid() )
1253 nFirstInvalid = nLine;
1254 break;
1258 for ( nLastInvalid = nFirstInvalid; nLastInvalid < nLines; nLastInvalid++ )
1260 TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
1261 if ( rL.IsValid() )
1262 break;
1265 if ( nLastInvalid >= nLines )
1266 nLastInvalid = nLines-1;
1268 return Range( nFirstInvalid*mnCharHeight, ((nLastInvalid+1)*mnCharHeight)-1 );
1271 sal_uInt32 TextEngine::GetParagraphCount() const
1273 return static_cast<sal_uInt32>(mpDoc->GetNodes().size());
1276 void TextEngine::EnableUndo( bool bEnable )
1278 // delete list when switching mode
1279 if ( bEnable != IsUndoEnabled() )
1280 ResetUndo();
1282 mbUndoEnabled = bEnable;
1285 SfxUndoManager& TextEngine::GetUndoManager()
1287 if ( !mpUndoManager )
1288 mpUndoManager.reset( new TextUndoManager( this ) );
1289 return *mpUndoManager;
1292 void TextEngine::UndoActionStart( sal_uInt16 nId )
1294 if ( IsUndoEnabled() && !IsInUndo() )
1296 GetUndoManager().EnterListAction( OUString(), OUString(), nId, ViewShellId(-1) );
1300 void TextEngine::UndoActionEnd()
1302 if ( IsUndoEnabled() && !IsInUndo() )
1303 GetUndoManager().LeaveListAction();
1306 void TextEngine::InsertUndo( std::unique_ptr<TextUndo> pUndo, bool bTryMerge )
1308 SAL_WARN_IF( IsInUndo(), "vcl", "InsertUndo: in Undo mode!" );
1309 GetUndoManager().AddUndoAction( std::move(pUndo), bTryMerge );
1312 void TextEngine::ResetUndo()
1314 if ( mpUndoManager )
1315 mpUndoManager->Clear();
1318 void TextEngine::InsertContent( std::unique_ptr<TextNode> pNode, sal_uInt32 nPara )
1320 SAL_WARN_IF( !pNode, "vcl", "InsertContent: NULL-Pointer!" );
1321 SAL_WARN_IF( !IsInUndo(), "vcl", "InsertContent: only in Undo()!" );
1322 TEParaPortion* pNew = new TEParaPortion( pNode.get() );
1323 mpTEParaPortions->Insert( pNew, nPara );
1324 mpDoc->GetNodes().insert( mpDoc->GetNodes().begin() + nPara, std::move(pNode) );
1325 ImpParagraphInserted( nPara );
1328 TextPaM TextEngine::SplitContent( sal_uInt32 nNode, sal_Int32 nSepPos )
1330 #ifdef DBG_UTIL
1331 TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
1332 SAL_WARN_IF( !pNode, "vcl", "SplitContent: Invalid Node!" );
1333 SAL_WARN_IF( !IsInUndo(), "vcl", "SplitContent: only in Undo()!" );
1334 SAL_WARN_IF( nSepPos > pNode->GetText().getLength(), "vcl", "SplitContent: Bad index" );
1335 #endif
1336 TextPaM aPaM( nNode, nSepPos );
1337 return ImpInsertParaBreak( aPaM );
1340 TextPaM TextEngine::ConnectContents( sal_uInt32 nLeftNode )
1342 SAL_WARN_IF( !IsInUndo(), "vcl", "ConnectContent: only in Undo()!" );
1343 return ImpConnectParagraphs( nLeftNode, nLeftNode+1 );
1346 void TextEngine::SeekCursor( sal_uInt32 nPara, sal_Int32 nPos, vcl::Font& rFont, OutputDevice* pOutDev )
1348 rFont = maFont;
1349 if ( pOutDev )
1350 pOutDev->SetTextColor( maTextColor );
1352 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1353 sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
1354 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
1356 TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
1357 if ( rAttrib.GetStart() > nPos )
1358 break;
1360 // When seeking don't use Attr that start there!
1361 // Do not use empty attributes:
1362 // - If just being setup and empty => no effect on Font
1363 // - Characters that are setup in an empty paragraph become visible right away.
1364 if ( ( ( rAttrib.GetStart() < nPos ) && ( rAttrib.GetEnd() >= nPos ) )
1365 || pNode->GetText().isEmpty() )
1367 if ( rAttrib.Which() != TEXTATTR_FONTCOLOR )
1369 rAttrib.GetAttr().SetFont(rFont);
1371 else
1373 if ( pOutDev )
1374 pOutDev->SetTextColor( static_cast<const TextAttribFontColor&>(rAttrib.GetAttr()).GetColor() );
1379 if ( mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) &&
1380 ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) ) )
1382 ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
1383 if ( nAttr & ExtTextInputAttr::Underline )
1384 rFont.SetUnderline( LINESTYLE_SINGLE );
1385 else if ( nAttr & ExtTextInputAttr::BoldUnderline )
1386 rFont.SetUnderline( LINESTYLE_BOLD );
1387 else if ( nAttr & ExtTextInputAttr::DottedUnderline )
1388 rFont.SetUnderline( LINESTYLE_DOTTED );
1389 else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
1390 rFont.SetUnderline( LINESTYLE_DOTTED );
1391 if ( nAttr & ExtTextInputAttr::RedText )
1392 rFont.SetColor( COL_RED );
1393 else if ( nAttr & ExtTextInputAttr::HalfToneText )
1394 rFont.SetColor( COL_LIGHTGRAY );
1395 if ( nAttr & ExtTextInputAttr::Highlight )
1397 const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
1398 rFont.SetColor( rStyleSettings.GetHighlightTextColor() );
1399 rFont.SetFillColor( rStyleSettings.GetHighlightColor() );
1400 rFont.SetTransparent( false );
1402 else if ( nAttr & ExtTextInputAttr::GrayWaveline )
1404 rFont.SetUnderline( LINESTYLE_WAVE );
1405 // if( pOut )
1406 // pOut->SetTextLineColor( COL_LIGHTGRAY );
1411 void TextEngine::FormatAndUpdate( TextView* pCurView )
1413 if ( mbDowning )
1414 return;
1416 if ( IsInUndo() )
1417 IdleFormatAndUpdate( pCurView );
1418 else
1420 FormatDoc();
1421 UpdateViews( pCurView );
1425 void TextEngine::IdleFormatAndUpdate( TextView* pCurView, sal_uInt16 nMaxTimerRestarts )
1427 mpIdleFormatter->DoIdleFormat( pCurView, nMaxTimerRestarts );
1430 void TextEngine::TextModified()
1432 mbFormatted = false;
1433 mbModified = true;
1436 void TextEngine::UpdateViews( TextView* pCurView )
1438 if ( !GetUpdateMode() || IsFormatting() || maInvalidRect.IsEmpty() )
1439 return;
1441 SAL_WARN_IF( !IsFormatted(), "vcl", "UpdateViews: Doc not formatted!" );
1443 for (TextView* pView : *mpViews)
1445 pView->HideCursor();
1447 tools::Rectangle aClipRect( maInvalidRect );
1448 const Size aOutSz = pView->GetWindow()->GetOutputSizePixel();
1449 const tools::Rectangle aVisArea( pView->GetStartDocPos(), aOutSz );
1450 aClipRect.Intersection( aVisArea );
1451 if ( !aClipRect.IsEmpty() )
1453 // translate into window coordinates
1454 Point aNewPos = pView->GetWindowPos( aClipRect.TopLeft() );
1455 if ( IsRightToLeft() )
1456 aNewPos.AdjustX( -(aOutSz.Width() - 1) );
1457 aClipRect.SetPos( aNewPos );
1459 pView->GetWindow()->Invalidate( aClipRect );
1463 if ( pCurView )
1465 pCurView->ShowCursor( pCurView->IsAutoScroll() );
1468 maInvalidRect = tools::Rectangle();
1471 IMPL_LINK_NOARG(TextEngine, IdleFormatHdl, Timer *, void)
1473 FormatAndUpdate( mpIdleFormatter->GetView() );
1476 void TextEngine::CheckIdleFormatter()
1478 mpIdleFormatter->ForceTimeout();
1481 void TextEngine::FormatFullDoc()
1483 for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
1485 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
1486 pTEParaPortion->MarkSelectionInvalid( 0 );
1488 mbFormatted = false;
1489 FormatDoc();
1492 void TextEngine::FormatDoc()
1494 if ( IsFormatted() || !GetUpdateMode() || IsFormatting() )
1495 return;
1497 mbIsFormatting = true;
1498 mbHasMultiLineParas = false;
1500 long nY = 0;
1501 bool bGrow = false;
1503 maInvalidRect = tools::Rectangle(); // clear
1504 for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
1506 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1507 if ( pTEParaPortion->IsInvalid() )
1509 const long nOldParaWidth = mnCurTextWidth >= 0 ? CalcTextWidth( nPara ) : -1;
1511 Broadcast( TextHint( SfxHintId::TextFormatPara, nPara ) );
1513 if ( CreateLines( nPara ) )
1514 bGrow = true;
1516 // set InvalidRect only once
1517 if ( maInvalidRect.IsEmpty() )
1519 // otherwise remains Empty() for Paperwidth 0 (AutoPageSize)
1520 const long nWidth = mnMaxTextWidth
1521 ? mnMaxTextWidth
1522 : std::numeric_limits<long>::max();
1523 const Range aInvRange( GetInvalidYOffsets( nPara ) );
1524 maInvalidRect = tools::Rectangle( Point( 0, nY+aInvRange.Min() ),
1525 Size( nWidth, aInvRange.Len() ) );
1527 else
1529 maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
1532 if ( mnCurTextWidth >= 0 )
1534 const long nNewParaWidth = CalcTextWidth( nPara );
1535 if ( nNewParaWidth >= mnCurTextWidth )
1536 mnCurTextWidth = nNewParaWidth;
1537 else if ( nOldParaWidth >= mnCurTextWidth )
1538 mnCurTextWidth = -1;
1541 else if ( bGrow )
1543 maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
1545 nY += CalcParaHeight( nPara );
1546 if ( !mbHasMultiLineParas && pTEParaPortion->GetLines().size() > 1 )
1547 mbHasMultiLineParas = true;
1550 if ( !maInvalidRect.IsEmpty() )
1552 const long nNewHeight = CalcTextHeight();
1553 const long nDiff = nNewHeight - mnCurTextHeight;
1554 if ( nNewHeight < mnCurTextHeight )
1556 maInvalidRect.SetBottom( std::max( nNewHeight, mnCurTextHeight ) );
1557 if ( maInvalidRect.IsEmpty() )
1559 maInvalidRect.SetTop( 0 );
1560 // Left and Right are not evaluated, but set because of IsEmpty
1561 maInvalidRect.SetLeft( 0 );
1562 maInvalidRect.SetRight( mnMaxTextWidth );
1566 mnCurTextHeight = nNewHeight;
1567 if ( nDiff )
1569 mbFormatted = true;
1570 Broadcast( TextHint( SfxHintId::TextHeightChanged ) );
1574 mbIsFormatting = false;
1575 mbFormatted = true;
1577 Broadcast( TextHint( SfxHintId::TextFormatted ) );
1580 void TextEngine::CreateAndInsertEmptyLine( sal_uInt32 nPara )
1582 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1583 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1585 TextLine aTmpLine;
1586 aTmpLine.SetStart( pNode->GetText().getLength() );
1587 aTmpLine.SetEnd( aTmpLine.GetStart() );
1589 if ( ImpGetAlign() == TxtAlign::Center )
1590 aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth / 2) );
1591 else if ( ImpGetAlign() == TxtAlign::Right )
1592 aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth) );
1593 else
1594 aTmpLine.SetStartX( mpDoc->GetLeftMargin() );
1596 bool bLineBreak = !pNode->GetText().isEmpty();
1598 std::unique_ptr<TETextPortion> pDummyPortion(new TETextPortion( 0 ));
1599 pDummyPortion->GetWidth() = 0;
1600 pTEParaPortion->GetTextPortions().push_back( std::move(pDummyPortion) );
1602 if ( bLineBreak )
1604 // -2: The new one is already inserted.
1605 const std::size_t nPos = pTEParaPortion->GetTextPortions().size() - 1;
1606 aTmpLine.SetStartPortion( nPos );
1607 aTmpLine.SetEndPortion( nPos );
1609 pTEParaPortion->GetLines().push_back( aTmpLine );
1612 void TextEngine::ImpBreakLine( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nPortionStart, long nRemainingWidth )
1614 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
1616 // Font still should be adjusted
1617 sal_Int32 nMaxBreakPos = mpRefDev->GetTextBreak( pNode->GetText(), nRemainingWidth, nPortionStart );
1619 SAL_WARN_IF( nMaxBreakPos >= pNode->GetText().getLength(), "vcl", "ImpBreakLine: Break?!" );
1621 if ( nMaxBreakPos == -1 ) // GetTextBreak() != GetTextSize()
1622 nMaxBreakPos = pNode->GetText().getLength() - 1;
1624 uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
1625 i18n::LineBreakHyphenationOptions aHyphOptions( nullptr, uno::Sequence< beans::PropertyValue >(), 1 );
1627 i18n::LineBreakUserOptions aUserOptions;
1628 aUserOptions.forbiddenBeginCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().beginLine;
1629 aUserOptions.forbiddenEndCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().endLine;
1630 aUserOptions.applyForbiddenRules = true;
1631 aUserOptions.allowPunctuationOutsideMargin = false;
1632 aUserOptions.allowHyphenateEnglish = false;
1634 static const css::lang::Locale aDefLocale;
1635 i18n::LineBreakResults aLBR = xBI->getLineBreak( pNode->GetText(), nMaxBreakPos, aDefLocale, pLine->GetStart(), aHyphOptions, aUserOptions );
1636 sal_Int32 nBreakPos = aLBR.breakIndex;
1637 if ( nBreakPos <= pLine->GetStart() )
1639 nBreakPos = nMaxBreakPos;
1640 if ( nBreakPos <= pLine->GetStart() )
1641 nBreakPos = pLine->GetStart() + 1; // infinite loop otherwise!
1644 // the damaged Portion is the End Portion
1645 pLine->SetEnd( nBreakPos );
1646 const std::size_t nEndPortion = SplitTextPortion( nPara, nBreakPos );
1648 if ( nBreakPos >= pLine->GetStart() &&
1649 nBreakPos < pNode->GetText().getLength() &&
1650 pNode->GetText()[ nBreakPos ] == ' ' )
1652 // generally suppress blanks at the end of line
1653 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1654 TETextPortion* pTP = pTEParaPortion->GetTextPortions()[ nEndPortion ];
1655 SAL_WARN_IF( nBreakPos <= pLine->GetStart(), "vcl", "ImpBreakLine: SplitTextPortion at beginning of line?" );
1656 pTP->GetWidth() = CalcTextWidth( nPara, nBreakPos-pTP->GetLen(), pTP->GetLen()-1 );
1658 pLine->SetEndPortion( nEndPortion );
1661 std::size_t TextEngine::SplitTextPortion( sal_uInt32 nPara, sal_Int32 nPos )
1664 // the Portion at nPos is being split, unless there is already a switch at nPos
1665 if ( nPos == 0 )
1666 return 0;
1668 std::size_t nSplitPortion;
1669 sal_Int32 nTmpPos = 0;
1670 TETextPortion* pTextPortion = nullptr;
1671 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1672 const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
1673 for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ )
1675 TETextPortion* pTP = pTEParaPortion->GetTextPortions()[nSplitPortion];
1676 nTmpPos += pTP->GetLen();
1677 if ( nTmpPos >= nPos )
1679 if ( nTmpPos == nPos ) // nothing needs splitting
1680 return nSplitPortion;
1681 pTextPortion = pTP;
1682 break;
1686 SAL_WARN_IF( !pTextPortion, "vcl", "SplitTextPortion: position outside of region!" );
1688 const sal_Int32 nOverlapp = nTmpPos - nPos;
1689 pTextPortion->GetLen() -= nOverlapp;
1690 std::unique_ptr<TETextPortion> pNewPortion( new TETextPortion( nOverlapp ) );
1691 pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nSplitPortion + 1, std::move(pNewPortion) );
1692 pTextPortion->GetWidth() = CalcTextWidth( nPara, nPos-pTextPortion->GetLen(), pTextPortion->GetLen() );
1694 return nSplitPortion;
1697 void TextEngine::CreateTextPortions( sal_uInt32 nPara, sal_Int32 nStartPos )
1699 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1700 TextNode* pNode = pTEParaPortion->GetNode();
1701 SAL_WARN_IF( pNode->GetText().isEmpty(), "vcl", "CreateTextPortions: should not be used for empty paragraphs!" );
1703 std::set<sal_Int32> aPositions;
1704 std::set<sal_Int32>::iterator aPositionsIt;
1705 aPositions.insert(0);
1707 const sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
1708 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
1710 TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
1712 aPositions.insert( rAttrib.GetStart() );
1713 aPositions.insert( rAttrib.GetEnd() );
1715 aPositions.insert( pNode->GetText().getLength() );
1717 const std::vector<TEWritingDirectionInfo>& rWritingDirections = pTEParaPortion->GetWritingDirectionInfos();
1718 for ( const auto& rWritingDirection : rWritingDirections )
1719 aPositions.insert( rWritingDirection.nStartPos );
1721 if ( mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) )
1723 ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xffff);
1724 for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ )
1726 if ( mpIMEInfos->pAttribs[n] != nLastAttr )
1728 aPositions.insert( mpIMEInfos->aPos.GetIndex() + n );
1729 nLastAttr = mpIMEInfos->pAttribs[n];
1734 sal_Int32 nTabPos = pNode->GetText().indexOf( '\t' );
1735 while ( nTabPos != -1 )
1737 aPositions.insert( nTabPos );
1738 aPositions.insert( nTabPos + 1 );
1739 nTabPos = pNode->GetText().indexOf( '\t', nTabPos+1 );
1742 // Delete starting with...
1743 // Unfortunately, the number of TextPortions does not have to be
1744 // equal to aPositions.Count(), because of linebreaks
1745 sal_Int32 nPortionStart = 0;
1746 std::size_t nInvPortion = 0;
1747 std::size_t nP;
1748 for ( nP = 0; nP < pTEParaPortion->GetTextPortions().size(); nP++ )
1750 TETextPortion* pTmpPortion = pTEParaPortion->GetTextPortions()[nP];
1751 nPortionStart += pTmpPortion->GetLen();
1752 if ( nPortionStart >= nStartPos )
1754 nPortionStart -= pTmpPortion->GetLen();
1755 nInvPortion = nP;
1756 break;
1759 OSL_ENSURE(nP < pTEParaPortion->GetTextPortions().size()
1760 || pTEParaPortion->GetTextPortions().empty(),
1761 "CreateTextPortions: Nothing to delete!");
1762 if ( nInvPortion && ( nPortionStart+pTEParaPortion->GetTextPortions()[nInvPortion]->GetLen() > nStartPos ) )
1764 // better one before...
1765 // But only if it was within the Portion; otherwise it might be
1766 // the only one in the previous line!
1767 nInvPortion--;
1768 nPortionStart -= pTEParaPortion->GetTextPortions()[nInvPortion]->GetLen();
1770 pTEParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion );
1772 // a Portion might have been created by a line break
1773 aPositions.insert( nPortionStart );
1775 aPositionsIt = aPositions.find( nPortionStart );
1776 SAL_WARN_IF( aPositionsIt == aPositions.end(), "vcl", "CreateTextPortions: nPortionStart not found" );
1778 if ( aPositionsIt != aPositions.end() )
1780 std::set<sal_Int32>::iterator nextIt = aPositionsIt;
1781 for ( ++nextIt; nextIt != aPositions.end(); ++aPositionsIt, ++nextIt )
1783 std::unique_ptr<TETextPortion> pNew( new TETextPortion( *nextIt - *aPositionsIt ) );
1784 pTEParaPortion->GetTextPortions().push_back( std::move(pNew) );
1787 OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "CreateTextPortions: No Portions?!");
1790 void TextEngine::RecalcTextPortion( sal_uInt32 nPara, sal_Int32 nStartPos, sal_Int32 nNewChars )
1792 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
1793 OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "RecalcTextPortion: no Portions!");
1794 OSL_ENSURE(nNewChars, "RecalcTextPortion: Diff == 0");
1796 TextNode* const pNode = pTEParaPortion->GetNode();
1797 if ( nNewChars > 0 )
1799 // If an Attribute is starting/ending at nStartPos, or there is a tab
1800 // before nStartPos => a new Portion starts.
1801 // Otherwise the Portion is extended at nStartPos.
1802 // Or if at the very beginning ( StartPos 0 ) followed by a tab...
1803 if ( ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) ) ||
1804 ( nStartPos && ( pNode->GetText()[ nStartPos - 1 ] == '\t' ) ) ||
1805 ( !nStartPos && ( nNewChars < pNode->GetText().getLength() ) && pNode->GetText()[ nNewChars ] == '\t' ) )
1807 std::size_t nNewPortionPos = 0;
1808 if ( nStartPos )
1809 nNewPortionPos = SplitTextPortion( nPara, nStartPos ) + 1;
1811 // Here could be an empty Portion if the paragraph was empty,
1812 // or a new line was created by a hard line-break.
1813 if ( ( nNewPortionPos < pTEParaPortion->GetTextPortions().size() ) &&
1814 !pTEParaPortion->GetTextPortions()[nNewPortionPos]->GetLen() )
1816 // use the empty Portion
1817 pTEParaPortion->GetTextPortions()[nNewPortionPos]->GetLen() = nNewChars;
1819 else
1821 std::unique_ptr<TETextPortion> pNewPortion(new TETextPortion( nNewChars ));
1822 pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nNewPortionPos, std::move(pNewPortion) );
1825 else
1827 sal_Int32 nPortionStart {0};
1828 const std::size_t nTP = pTEParaPortion->GetTextPortions().FindPortion( nStartPos, nPortionStart );
1829 TETextPortion* const pTP = pTEParaPortion->GetTextPortions()[ nTP ];
1830 SAL_WARN_IF( !pTP, "vcl", "RecalcTextPortion: Portion not found!" );
1831 pTP->GetLen() += nNewChars;
1832 pTP->GetWidth() = -1;
1835 else
1837 // Shrink or remove Portion
1838 // Before calling this function, ensure that no Portions were in the deleted range!
1840 // There must be no Portion reaching into or starting within,
1841 // thus: nStartPos <= nPos <= nStartPos - nNewChars(neg.)
1842 std::size_t nPortion = 0;
1843 sal_Int32 nPos = 0;
1844 const sal_Int32 nEnd = nStartPos-nNewChars;
1845 const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
1846 TETextPortion* pTP = nullptr;
1847 for ( nPortion = 0; nPortion < nPortions; nPortion++ )
1849 pTP = pTEParaPortion->GetTextPortions()[ nPortion ];
1850 if ( ( nPos+pTP->GetLen() ) > nStartPos )
1852 SAL_WARN_IF( nPos > nStartPos, "vcl", "RecalcTextPortion: Bad Start!" );
1853 SAL_WARN_IF( nPos+pTP->GetLen() < nEnd, "vcl", "RecalcTextPortion: Bad End!" );
1854 break;
1856 nPos += pTP->GetLen();
1858 SAL_WARN_IF( !pTP, "vcl", "RecalcTextPortion: Portion not found!" );
1859 if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
1861 // remove Portion
1862 pTEParaPortion->GetTextPortions().erase( pTEParaPortion->GetTextPortions().begin() + nPortion );
1864 else
1866 SAL_WARN_IF( pTP->GetLen() <= (-nNewChars), "vcl", "RecalcTextPortion: Portion too small to shrink!" );
1867 pTP->GetLen() += nNewChars;
1869 OSL_ENSURE( pTEParaPortion->GetTextPortions().size(),
1870 "RecalcTextPortion: none are left!" );
1874 void TextEngine::ImpPaint( OutputDevice* pOutDev, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection )
1876 if ( !GetUpdateMode() )
1877 return;
1879 if ( !IsFormatted() )
1880 FormatDoc();
1882 vcl::Window* const pOutWin = dynamic_cast<vcl::Window*>(pOutDev);
1883 const bool bTransparent = (pOutWin && pOutWin->IsPaintTransparent());
1885 long nY = rStartPos.Y();
1887 TextPaM const* pSelStart = nullptr;
1888 TextPaM const* pSelEnd = nullptr;
1889 if ( pSelection && pSelection->HasRange() )
1891 const bool bInvers = pSelection->GetEnd() < pSelection->GetStart();
1892 pSelStart = !bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
1893 pSelEnd = bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
1896 const StyleSettings& rStyleSettings = pOutDev->GetSettings().GetStyleSettings();
1898 // for all paragraphs
1899 for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
1901 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
1902 // in case while typing Idle-Formatting, asynchronous Paint
1903 if ( pPortion->IsInvalid() )
1904 return;
1906 const long nParaHeight = CalcParaHeight( nPara );
1907 if ( !pPaintArea || ( ( nY + nParaHeight ) > pPaintArea->Top() ) )
1909 // for all lines of the paragraph
1910 sal_Int32 nIndex = 0;
1911 for ( auto & rLine : pPortion->GetLines() )
1913 Point aTmpPos( rStartPos.X() + rLine.GetStartX(), nY );
1915 if ( !pPaintArea || ( ( nY + mnCharHeight ) > pPaintArea->Top() ) )
1917 // for all Portions of the line
1918 nIndex = rLine.GetStart();
1919 for ( std::size_t y = rLine.GetStartPortion(); y <= rLine.GetEndPortion(); y++ )
1921 OSL_ENSURE(pPortion->GetTextPortions().size(),
1922 "ImpPaint: Line without Textportion!");
1923 TETextPortion* pTextPortion = pPortion->GetTextPortions()[ y ];
1924 SAL_WARN_IF( !pTextPortion, "vcl", "ImpPaint: Bad pTextPortion!" );
1926 ImpInitLayoutMode( pOutDev /*, pTextPortion->IsRightToLeft() */);
1928 const long nTxtWidth = pTextPortion->GetWidth();
1929 aTmpPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nIndex, nIndex ) );
1931 // only print if starting in the visible region
1932 if ( ( aTmpPos.X() + nTxtWidth ) >= 0 )
1934 switch ( pTextPortion->GetKind() )
1936 case PORTIONKIND_TEXT:
1938 vcl::Font aFont;
1939 SeekCursor( nPara, nIndex+1, aFont, pOutDev );
1940 if( bTransparent )
1941 aFont.SetTransparent( true );
1942 else if ( pSelection )
1943 aFont.SetTransparent( false );
1944 pOutDev->SetFont( aFont );
1946 sal_Int32 nTmpIndex = nIndex;
1947 sal_Int32 nEnd = nTmpIndex + pTextPortion->GetLen();
1948 Point aPos = aTmpPos;
1950 bool bDone = false;
1951 if ( pSelStart )
1953 // is a part of it in the selection?
1954 const TextPaM aTextStart( nPara, nTmpIndex );
1955 const TextPaM aTextEnd( nPara, nEnd );
1956 if ( ( aTextStart < *pSelEnd ) && ( aTextEnd > *pSelStart ) )
1958 // 1) vcl::Region before Selection
1959 if ( aTextStart < *pSelStart )
1961 const sal_Int32 nL = pSelStart->GetIndex() - nTmpIndex;
1962 pOutDev->SetFont( aFont);
1963 pOutDev->SetTextFillColor();
1964 aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
1965 pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
1966 nTmpIndex = nTmpIndex + nL;
1969 // 2) vcl::Region with Selection
1970 sal_Int32 nL = nEnd - nTmpIndex;
1971 if ( aTextEnd > *pSelEnd )
1972 nL = pSelEnd->GetIndex() - nTmpIndex;
1973 if ( nL )
1975 const Color aOldTextColor = pOutDev->GetTextColor();
1976 pOutDev->SetTextColor( rStyleSettings.GetHighlightTextColor() );
1977 pOutDev->SetTextFillColor( rStyleSettings.GetHighlightColor() );
1978 aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
1979 pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
1980 pOutDev->SetTextColor( aOldTextColor );
1981 pOutDev->SetTextFillColor();
1982 nTmpIndex = nTmpIndex + nL;
1985 // 3) vcl::Region after Selection
1986 if ( nTmpIndex < nEnd )
1988 nL = nEnd-nTmpIndex;
1989 pOutDev->SetTextFillColor();
1990 aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
1991 pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
1993 bDone = true;
1996 if ( !bDone )
1998 pOutDev->SetTextFillColor();
1999 aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nEnd ) );
2000 pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
2003 break;
2004 case PORTIONKIND_TAB:
2005 // for HideSelection() only Range, pSelection = 0.
2006 if ( pSelStart ) // also implies pSelEnd
2008 const tools::Rectangle aTabArea( aTmpPos, Point( aTmpPos.X()+nTxtWidth, aTmpPos.Y()+mnCharHeight-1 ) );
2009 // is the Tab in the Selection???
2010 const TextPaM aTextStart(nPara, nIndex);
2011 const TextPaM aTextEnd(nPara, nIndex + 1);
2012 if ((aTextStart < *pSelEnd) && (aTextEnd > *pSelStart))
2014 const Color aOldColor = pOutDev->GetFillColor();
2015 pOutDev->SetFillColor(
2016 rStyleSettings.GetHighlightColor());
2017 pOutDev->DrawRect(aTabArea);
2018 pOutDev->SetFillColor(aOldColor);
2020 else
2022 pOutDev->Erase( aTabArea );
2025 break;
2026 default:
2027 OSL_FAIL( "ImpPaint: Unknown Portion-Type !" );
2031 nIndex += pTextPortion->GetLen();
2035 nY += mnCharHeight;
2037 if ( pPaintArea && ( nY >= pPaintArea->Bottom() ) )
2038 break; // no more visible actions
2041 else
2043 nY += nParaHeight;
2046 if ( pPaintArea && ( nY > pPaintArea->Bottom() ) )
2047 break; // no more visible actions
2051 bool TextEngine::CreateLines( sal_uInt32 nPara )
2053 // bool: changing Height of Paragraph Yes/No - true/false
2055 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2056 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
2057 SAL_WARN_IF( !pTEParaPortion->IsInvalid(), "vcl", "CreateLines: Portion not invalid!" );
2059 const auto nOldLineCount = pTEParaPortion->GetLines().size();
2061 // fast special case for empty paragraphs
2062 if ( pTEParaPortion->GetNode()->GetText().isEmpty() )
2064 if ( !pTEParaPortion->GetTextPortions().empty() )
2065 pTEParaPortion->GetTextPortions().Reset();
2066 pTEParaPortion->GetLines().clear();
2067 CreateAndInsertEmptyLine( nPara );
2068 pTEParaPortion->SetValid();
2069 return nOldLineCount != pTEParaPortion->GetLines().size();
2072 // initialization
2073 if ( pTEParaPortion->GetLines().empty() )
2075 pTEParaPortion->GetLines().emplace_back( );
2078 const sal_Int32 nInvalidDiff = pTEParaPortion->GetInvalidDiff();
2079 const sal_Int32 nInvalidStart = pTEParaPortion->GetInvalidPosStart();
2080 const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff );
2081 bool bQuickFormat = false;
2083 if ( pTEParaPortion->GetWritingDirectionInfos().empty() )
2084 ImpInitWritingDirections( nPara );
2086 if ( pTEParaPortion->GetWritingDirectionInfos().size() == 1 && pTEParaPortion->IsSimpleInvalid() )
2088 bQuickFormat = nInvalidDiff != 0;
2089 if ( nInvalidDiff < 0 )
2091 // check if deleting across Portion border
2092 sal_Int32 nPos = 0;
2093 for ( const auto & pTP : pTEParaPortion->GetTextPortions() )
2095 // there must be no Start/End in the deleted region
2096 nPos += pTP->GetLen();
2097 if ( nPos > nInvalidStart && nPos < nInvalidEnd )
2099 bQuickFormat = false;
2100 break;
2106 if ( bQuickFormat )
2107 RecalcTextPortion( nPara, nInvalidStart, nInvalidDiff );
2108 else
2109 CreateTextPortions( nPara, nInvalidStart );
2111 // search for line with InvalidPos; start a line prior
2112 // flag lines => do not remove!
2114 sal_uInt16 nLine = pTEParaPortion->GetLines().size()-1;
2115 for ( sal_uInt16 nL = 0; nL <= nLine; nL++ )
2117 TextLine& rLine = pTEParaPortion->GetLines()[ nL ];
2118 if ( rLine.GetEnd() > nInvalidStart )
2120 nLine = nL;
2121 break;
2123 rLine.SetValid();
2125 // start a line before...
2126 // if typing at the end, the line before cannot change
2127 if ( nLine && ( !pTEParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->GetText().getLength() ) || ( nInvalidDiff <= 0 ) ) )
2128 nLine--;
2130 TextLine* pLine = &( pTEParaPortion->GetLines()[ nLine ] );
2132 // format all lines starting here
2133 std::size_t nDelFromLine = TETextPortionList::npos;
2135 sal_Int32 nIndex = pLine->GetStart();
2136 TextLine aSaveLine( *pLine );
2138 vcl::Font aFont;
2140 while ( nIndex < pNode->GetText().getLength() )
2142 bool bEOL = false;
2143 sal_Int32 nPortionStart = 0;
2144 sal_Int32 nPortionEnd = 0;
2146 sal_Int32 nTmpPos = nIndex;
2147 std::size_t nTmpPortion = pLine->GetStartPortion();
2148 long nTmpWidth = mpDoc->GetLeftMargin();
2149 // do not subtract margin; it is included in TmpWidth
2150 long nXWidth = std::max(
2151 mnMaxTextWidth ? mnMaxTextWidth : std::numeric_limits<long>::max(), nTmpWidth);
2153 // search for Portion that does not fit anymore into line
2154 TETextPortion* pPortion = nullptr;
2155 bool bBrokenLine = false;
2157 while ( ( nTmpWidth <= nXWidth ) && !bEOL && ( nTmpPortion < pTEParaPortion->GetTextPortions().size() ) )
2159 nPortionStart = nTmpPos;
2160 pPortion = pTEParaPortion->GetTextPortions()[ nTmpPortion ];
2161 SAL_WARN_IF( !pPortion->GetLen(), "vcl", "CreateLines: Empty Portion!" );
2162 if ( pNode->GetText()[ nTmpPos ] == '\t' )
2164 long nCurPos = nTmpWidth-mpDoc->GetLeftMargin();
2165 nTmpWidth = ((nCurPos/mnDefTab)+1)*mnDefTab+mpDoc->GetLeftMargin();
2166 pPortion->GetWidth() = nTmpWidth - nCurPos - mpDoc->GetLeftMargin();
2167 // infinite loop, if this is the first token of the line and nTmpWidth > aPaperSize.Width !!!
2168 if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
2170 // adjust Tab
2171 pPortion->GetWidth() = nXWidth-1;
2172 nTmpWidth = pPortion->GetWidth();
2173 bEOL = true;
2174 bBrokenLine = true;
2176 pPortion->GetKind() = PORTIONKIND_TAB;
2178 else
2181 pPortion->GetWidth() = CalcTextWidth( nPara, nTmpPos, pPortion->GetLen() );
2182 nTmpWidth += pPortion->GetWidth();
2184 pPortion->SetRightToLeft( ImpGetRightToLeft( nPara, nTmpPos+1 ) );
2185 pPortion->GetKind() = PORTIONKIND_TEXT;
2188 nTmpPos += pPortion->GetLen();
2189 nPortionEnd = nTmpPos;
2190 nTmpPortion++;
2193 // this was perhaps one Portion too far
2194 bool bFixedEnd = false;
2195 if ( nTmpWidth > nXWidth )
2197 nPortionEnd = nTmpPos;
2198 nTmpPos -= pPortion->GetLen();
2199 nPortionStart = nTmpPos;
2200 nTmpPortion--;
2201 bEOL = false;
2203 nTmpWidth -= pPortion->GetWidth();
2204 if ( pPortion->GetKind() == PORTIONKIND_TAB )
2206 bEOL = true;
2207 bFixedEnd = true;
2210 else
2212 bEOL = true;
2213 pLine->SetEnd( nPortionEnd );
2214 OSL_ENSURE(pTEParaPortion->GetTextPortions().size(),
2215 "CreateLines: No TextPortions?");
2216 pLine->SetEndPortion( pTEParaPortion->GetTextPortions().size() - 1 );
2219 if ( bFixedEnd )
2221 pLine->SetEnd( nPortionStart );
2222 pLine->SetEndPortion( nTmpPortion-1 );
2224 else if ( bBrokenLine )
2226 pLine->SetEnd( nPortionStart+1 );
2227 pLine->SetEndPortion( nTmpPortion-1 );
2229 else if ( !bEOL )
2231 SAL_WARN_IF( (nPortionEnd-nPortionStart) != pPortion->GetLen(), "vcl", "CreateLines: There is a Portion after all?!" );
2232 const long nRemainingWidth = mnMaxTextWidth - nTmpWidth;
2233 ImpBreakLine( nPara, pLine, nPortionStart, nRemainingWidth );
2236 if ( ( ImpGetAlign() == TxtAlign::Center ) || ( ImpGetAlign() == TxtAlign::Right ) )
2238 // adjust
2239 long nTextWidth = 0;
2240 for ( std::size_t nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ )
2242 TETextPortion* pTextPortion = pTEParaPortion->GetTextPortions()[ nTP ];
2243 nTextWidth += pTextPortion->GetWidth();
2245 const long nSpace = mnMaxTextWidth - nTextWidth;
2246 if ( nSpace > 0 )
2248 if ( ImpGetAlign() == TxtAlign::Center )
2249 pLine->SetStartX( static_cast<sal_uInt16>(nSpace / 2) );
2250 else // TxtAlign::Right
2251 pLine->SetStartX( static_cast<sal_uInt16>(nSpace) );
2254 else
2256 pLine->SetStartX( mpDoc->GetLeftMargin() );
2259 // check if the line has to be printed again
2260 pLine->SetInvalid();
2262 if ( pTEParaPortion->IsSimpleInvalid() )
2264 // Change due to simple TextChange...
2265 // Do not abort formatting, as Portions might have to be split!
2266 // Once it is ok to abort, then validate the following lines!
2267 // But mark as valid, thus reduce printing...
2268 if ( pLine->GetEnd() < nInvalidStart )
2270 if ( *pLine == aSaveLine )
2272 pLine->SetValid();
2275 else
2277 const sal_Int32 nStart = pLine->GetStart();
2278 const sal_Int32 nEnd = pLine->GetEnd();
2280 if ( nStart > nInvalidEnd )
2282 if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) &&
2283 ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) )
2285 pLine->SetValid();
2286 if ( bQuickFormat )
2288 pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
2289 break;
2293 else if ( bQuickFormat && ( nEnd > nInvalidEnd) )
2295 // If the invalid line ends such that the next line starts
2296 // at the 'same' position as before (no change in line breaks),
2297 // the text width does not have to be recalculated.
2298 if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) )
2300 pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
2301 break;
2307 nIndex = pLine->GetEnd(); // next line Start = previous line End
2308 // because nEnd is past the last char!
2310 const std::size_t nEndPortion = pLine->GetEndPortion();
2312 // next line or new line
2313 pLine = nullptr;
2314 if ( nLine < pTEParaPortion->GetLines().size()-1 )
2315 pLine = &( pTEParaPortion->GetLines()[ ++nLine ] );
2316 if ( pLine && ( nIndex >= pNode->GetText().getLength() ) )
2318 nDelFromLine = nLine;
2319 break;
2321 if ( !pLine )
2323 if ( nIndex < pNode->GetText().getLength() )
2325 ++nLine;
2326 pTEParaPortion->GetLines().insert( pTEParaPortion->GetLines().begin() + nLine, TextLine() );
2327 pLine = &pTEParaPortion->GetLines()[nLine];
2329 else
2331 break;
2334 aSaveLine = *pLine;
2335 pLine->SetStart( nIndex );
2336 pLine->SetEnd( nIndex );
2337 pLine->SetStartPortion( nEndPortion+1 );
2338 pLine->SetEndPortion( nEndPortion+1 );
2340 } // while ( Index < Len )
2342 if (nDelFromLine != TETextPortionList::npos)
2344 pTEParaPortion->GetLines().erase( pTEParaPortion->GetLines().begin() + nDelFromLine,
2345 pTEParaPortion->GetLines().end() );
2348 SAL_WARN_IF( pTEParaPortion->GetLines().empty(), "vcl", "CreateLines: No Line!" );
2350 pTEParaPortion->SetValid();
2352 return nOldLineCount != pTEParaPortion->GetLines().size();
2355 OUString TextEngine::GetWord( const TextPaM& rCursorPos, TextPaM* pStartOfWord )
2357 OUString aWord;
2358 if ( rCursorPos.GetPara() < mpDoc->GetNodes().size() )
2360 TextSelection aSel( rCursorPos );
2361 TextNode* pNode = mpDoc->GetNodes()[ rCursorPos.GetPara() ].get();
2362 uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
2363 i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), rCursorPos.GetIndex(), GetLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
2364 aSel.GetStart().GetIndex() = aBoundary.startPos;
2365 aSel.GetEnd().GetIndex() = aBoundary.endPos;
2366 aWord = pNode->GetText().copy( aSel.GetStart().GetIndex(), aSel.GetEnd().GetIndex() - aSel.GetStart().GetIndex() );
2367 if ( pStartOfWord )
2368 *pStartOfWord = aSel.GetStart();
2370 return aWord;
2373 bool TextEngine::Read( SvStream& rInput, const TextSelection* pSel )
2375 const bool bUpdate = GetUpdateMode();
2376 SetUpdateMode( false );
2378 UndoActionStart();
2379 TextSelection aSel;
2380 if ( pSel )
2381 aSel = *pSel;
2382 else
2384 const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2385 TextNode* pNode = mpDoc->GetNodes()[ nParas - 1 ].get();
2386 aSel = TextPaM( nParas-1 , pNode->GetText().getLength() );
2389 if ( aSel.HasRange() )
2390 aSel = ImpDeleteText( aSel );
2392 OString aLine;
2393 bool bDone = rInput.ReadLine( aLine );
2394 OUString aTmpStr(OStringToOUString(aLine, rInput.GetStreamCharSet()));
2395 while ( bDone )
2397 aSel = ImpInsertText( aSel, aTmpStr );
2398 bDone = rInput.ReadLine( aLine );
2399 aTmpStr = OStringToOUString(aLine, rInput.GetStreamCharSet());
2400 if ( bDone )
2401 aSel = ImpInsertParaBreak( aSel.GetEnd() );
2404 UndoActionEnd();
2406 const TextSelection aNewSel( aSel.GetEnd(), aSel.GetEnd() );
2408 // so that FormatAndUpdate does not access the invalid selection
2409 if ( GetActiveView() )
2410 GetActiveView()->ImpSetSelection( aNewSel );
2412 SetUpdateMode( bUpdate );
2413 FormatAndUpdate( GetActiveView() );
2415 return rInput.GetError() == ERRCODE_NONE;
2418 void TextEngine::Write( SvStream& rOutput )
2420 TextSelection aSel;
2421 const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2422 TextNode* pSelNode = mpDoc->GetNodes()[ nParas - 1 ].get();
2423 aSel.GetStart() = TextPaM( 0, 0 );
2424 aSel.GetEnd() = TextPaM( nParas-1, pSelNode->GetText().getLength() );
2426 for ( sal_uInt32 nPara = aSel.GetStart().GetPara(); nPara <= aSel.GetEnd().GetPara(); ++nPara )
2428 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2430 const sal_Int32 nStartPos = nPara == aSel.GetStart().GetPara()
2431 ? aSel.GetStart().GetIndex() : 0;
2432 const sal_Int32 nEndPos = nPara == aSel.GetEnd().GetPara()
2433 ? aSel.GetEnd().GetIndex() : pNode->GetText().getLength();
2435 const OUString aText = pNode->GetText().copy( nStartPos, nEndPos-nStartPos );
2436 rOutput.WriteLine(OUStringToOString(aText, rOutput.GetStreamCharSet()));
2440 void TextEngine::RemoveAttribs( sal_uInt32 nPara )
2442 if ( nPara < mpDoc->GetNodes().size() )
2444 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2445 if ( pNode->GetCharAttribs().Count() )
2447 pNode->GetCharAttribs().Clear();
2449 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
2450 pTEParaPortion->MarkSelectionInvalid( 0 );
2452 mbFormatted = false;
2454 IdleFormatAndUpdate( nullptr, 0xFFFF );
2459 void TextEngine::SetAttrib( const TextAttrib& rAttr, sal_uInt32 nPara, sal_Int32 nStart, sal_Int32 nEnd )
2462 // For now do not check if Attributes overlap!
2463 // This function is for TextEditors that want to _quickly_ generate the Syntax-Highlight
2465 // As TextEngine is currently intended only for TextEditors, there is no Undo for Attributes!
2467 if ( nPara < mpDoc->GetNodes().size() )
2469 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2470 TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
2472 const sal_Int32 nMax = pNode->GetText().getLength();
2473 if ( nStart > nMax )
2474 nStart = nMax;
2475 if ( nEnd > nMax )
2476 nEnd = nMax;
2478 pNode->GetCharAttribs().InsertAttrib( std::make_unique<TextCharAttrib>( rAttr, nStart, nEnd ) );
2479 pTEParaPortion->MarkSelectionInvalid( nStart );
2481 mbFormatted = false;
2482 IdleFormatAndUpdate( nullptr, 0xFFFF );
2486 void TextEngine::SetTextAlign( TxtAlign eAlign )
2488 if ( eAlign != meAlign )
2490 meAlign = eAlign;
2491 FormatFullDoc();
2492 UpdateViews();
2496 void TextEngine::ValidateSelection( TextSelection& rSel ) const
2498 ValidatePaM( rSel.GetStart() );
2499 ValidatePaM( rSel.GetEnd() );
2502 void TextEngine::ValidatePaM( TextPaM& rPaM ) const
2504 const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2505 if ( rPaM.GetPara() >= nParas )
2507 rPaM.GetPara() = nParas ? nParas-1 : 0;
2508 rPaM.GetIndex() = TEXT_INDEX_ALL;
2511 const sal_Int32 nMaxIndex = GetTextLen( rPaM.GetPara() );
2512 if ( rPaM.GetIndex() > nMaxIndex )
2513 rPaM.GetIndex() = nMaxIndex;
2516 // adjust State & Selection
2518 void TextEngine::ImpParagraphInserted( sal_uInt32 nPara )
2520 // No adjustment needed for the active View;
2521 // but for all passive Views the Selection needs adjusting.
2522 if ( mpViews->size() > 1 )
2524 for ( auto nView = mpViews->size(); nView; )
2526 TextView* pView = (*mpViews)[ --nView ];
2527 if ( pView != GetActiveView() )
2529 for ( int n = 0; n <= 1; n++ )
2531 TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2532 if ( rPaM.GetPara() >= nPara )
2533 rPaM.GetPara()++;
2538 Broadcast( TextHint( SfxHintId::TextParaInserted, nPara ) );
2541 void TextEngine::ImpParagraphRemoved( sal_uInt32 nPara )
2543 if ( mpViews->size() > 1 )
2545 for ( auto nView = mpViews->size(); nView; )
2547 TextView* pView = (*mpViews)[ --nView ];
2548 if ( pView != GetActiveView() )
2550 const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
2551 for ( int n = 0; n <= 1; n++ )
2553 TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2554 if ( rPaM.GetPara() > nPara )
2555 rPaM.GetPara()--;
2556 else if ( rPaM.GetPara() == nPara )
2558 rPaM.GetIndex() = 0;
2559 if ( rPaM.GetPara() >= nParas )
2560 rPaM.GetPara()--;
2566 Broadcast( TextHint( SfxHintId::TextParaRemoved, nPara ) );
2569 void TextEngine::ImpCharsRemoved( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
2571 if ( mpViews->size() > 1 )
2573 for ( auto nView = mpViews->size(); nView; )
2575 TextView* pView = (*mpViews)[ --nView ];
2576 if ( pView != GetActiveView() )
2578 const sal_Int32 nEnd = nPos + nChars;
2579 for ( int n = 0; n <= 1; n++ )
2581 TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2582 if ( rPaM.GetPara() == nPara )
2584 if ( rPaM.GetIndex() > nEnd )
2585 rPaM.GetIndex() = rPaM.GetIndex() - nChars;
2586 else if ( rPaM.GetIndex() > nPos )
2587 rPaM.GetIndex() = nPos;
2593 Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
2596 void TextEngine::ImpCharsInserted( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
2598 if ( mpViews->size() > 1 )
2600 for ( auto nView = mpViews->size(); nView; )
2602 TextView* pView = (*mpViews)[ --nView ];
2603 if ( pView != GetActiveView() )
2605 for ( int n = 0; n <= 1; n++ )
2607 TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
2608 if ( rPaM.GetPara() == nPara )
2610 if ( rPaM.GetIndex() >= nPos )
2611 rPaM.GetIndex() += nChars;
2617 Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
2620 void TextEngine::Draw( OutputDevice* pDev, const Point& rPos )
2622 ImpPaint( pDev, rPos, nullptr );
2625 void TextEngine::SetLeftMargin( sal_uInt16 n )
2627 mpDoc->SetLeftMargin( n );
2630 uno::Reference< i18n::XBreakIterator > const & TextEngine::GetBreakIterator()
2632 if ( !mxBreakIterator.is() )
2633 mxBreakIterator = vcl::unohelper::CreateBreakIterator();
2634 SAL_WARN_IF( !mxBreakIterator.is(), "vcl", "BreakIterator: Failed to create!" );
2635 return mxBreakIterator;
2638 void TextEngine::SetLocale( const css::lang::Locale& rLocale )
2640 maLocale = rLocale;
2641 mpLocaleDataWrapper.reset();
2644 css::lang::Locale const & TextEngine::GetLocale()
2646 if ( maLocale.Language.isEmpty() )
2648 maLocale = Application::GetSettings().GetUILanguageTag().getLocale(); // TODO: why UI locale?
2650 return maLocale;
2653 LocaleDataWrapper* TextEngine::ImpGetLocaleDataWrapper()
2655 if ( !mpLocaleDataWrapper )
2656 mpLocaleDataWrapper.reset( new LocaleDataWrapper( LanguageTag( GetLocale()) ) );
2658 return mpLocaleDataWrapper.get();
2661 void TextEngine::SetRightToLeft( bool bR2L )
2663 if ( mbRightToLeft != bR2L )
2665 mbRightToLeft = bR2L;
2666 meAlign = bR2L ? TxtAlign::Right : TxtAlign::Left;
2667 FormatFullDoc();
2668 UpdateViews();
2672 void TextEngine::ImpInitWritingDirections( sal_uInt32 nPara )
2674 TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
2675 std::vector<TEWritingDirectionInfo>& rInfos = pParaPortion->GetWritingDirectionInfos();
2676 rInfos.clear();
2678 if ( !pParaPortion->GetNode()->GetText().isEmpty() )
2680 const UBiDiLevel nBidiLevel = IsRightToLeft() ? 1 /*RTL*/ : 0 /*LTR*/;
2681 OUString aText( pParaPortion->GetNode()->GetText() );
2683 // Bidi functions from icu 2.0
2685 UErrorCode nError = U_ZERO_ERROR;
2686 UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError );
2687 nError = U_ZERO_ERROR;
2689 ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError );
2690 nError = U_ZERO_ERROR;
2692 long nCount = ubidi_countRuns( pBidi, &nError );
2694 int32_t nStart = 0;
2695 int32_t nEnd;
2696 UBiDiLevel nCurrDir;
2698 for ( long nIdx = 0; nIdx < nCount; ++nIdx )
2700 ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
2701 // bit 0 of nCurrDir indicates direction
2702 rInfos.emplace_back( /*bLeftToRight*/ nCurrDir % 2 == 0, nStart, nEnd );
2703 nStart = nEnd;
2706 ubidi_close( pBidi );
2709 // No infos mean no CTL and default dir is L2R...
2710 if ( rInfos.empty() )
2711 rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->GetText().getLength() );
2715 bool TextEngine::ImpGetRightToLeft( sal_uInt32 nPara, sal_Int32 nPos )
2717 bool bRightToLeft = false;
2719 TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
2720 if ( pNode && !pNode->GetText().isEmpty() )
2722 TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
2723 if ( pParaPortion->GetWritingDirectionInfos().empty() )
2724 ImpInitWritingDirections( nPara );
2726 std::vector<TEWritingDirectionInfo>& rDirInfos = pParaPortion->GetWritingDirectionInfos();
2727 for ( const auto& rWritingDirectionInfo : rDirInfos )
2729 if ( rWritingDirectionInfo.nStartPos <= nPos && rWritingDirectionInfo.nEndPos >= nPos )
2731 bRightToLeft = !rWritingDirectionInfo.bLeftToRight;
2732 break;
2736 return bRightToLeft;
2739 long TextEngine::ImpGetPortionXOffset( sal_uInt32 nPara, TextLine const * pLine, std::size_t nTextPortion )
2741 long nX = pLine->GetStartX();
2743 TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
2745 for ( std::size_t i = pLine->GetStartPortion(); i < nTextPortion; i++ )
2747 TETextPortion* pPortion = pParaPortion->GetTextPortions()[ i ];
2748 nX += pPortion->GetWidth();
2751 TETextPortion* pDestPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
2752 if ( pDestPortion->GetKind() != PORTIONKIND_TAB )
2754 if ( !IsRightToLeft() && pDestPortion->IsRightToLeft() )
2756 // Portions behind must be added, visual before this portion
2757 std::size_t nTmpPortion = nTextPortion+1;
2758 while ( nTmpPortion <= pLine->GetEndPortion() )
2760 TETextPortion* pNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2761 if ( pNextTextPortion->IsRightToLeft() && ( pNextTextPortion->GetKind() != PORTIONKIND_TAB ) )
2762 nX += pNextTextPortion->GetWidth();
2763 else
2764 break;
2765 nTmpPortion++;
2767 // Portions before must be removed, visual behind this portion
2768 nTmpPortion = nTextPortion;
2769 while ( nTmpPortion > pLine->GetStartPortion() )
2771 --nTmpPortion;
2772 TETextPortion* pPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2773 if ( pPrevTextPortion->IsRightToLeft() && ( pPrevTextPortion->GetKind() != PORTIONKIND_TAB ) )
2774 nX -= pPrevTextPortion->GetWidth();
2775 else
2776 break;
2779 else if ( IsRightToLeft() && !pDestPortion->IsRightToLeft() )
2781 // Portions behind must be removed, visual behind this portion
2782 std::size_t nTmpPortion = nTextPortion+1;
2783 while ( nTmpPortion <= pLine->GetEndPortion() )
2785 TETextPortion* pNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2786 if ( !pNextTextPortion->IsRightToLeft() && ( pNextTextPortion->GetKind() != PORTIONKIND_TAB ) )
2787 nX += pNextTextPortion->GetWidth();
2788 else
2789 break;
2790 nTmpPortion++;
2792 // Portions before must be added, visual before this portion
2793 nTmpPortion = nTextPortion;
2794 while ( nTmpPortion > pLine->GetStartPortion() )
2796 --nTmpPortion;
2797 TETextPortion* pPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
2798 if ( !pPrevTextPortion->IsRightToLeft() && ( pPrevTextPortion->GetKind() != PORTIONKIND_TAB ) )
2799 nX -= pPrevTextPortion->GetWidth();
2800 else
2801 break;
2806 return nX;
2809 void TextEngine::ImpInitLayoutMode( OutputDevice* pOutDev )
2811 ComplexTextLayoutFlags nLayoutMode = pOutDev->GetLayoutMode();
2813 nLayoutMode &= ~ComplexTextLayoutFlags(ComplexTextLayoutFlags::BiDiRtl | ComplexTextLayoutFlags::BiDiStrong );
2815 pOutDev->SetLayoutMode( nLayoutMode );
2818 TxtAlign TextEngine::ImpGetAlign() const
2820 TxtAlign eAlign = meAlign;
2821 if ( IsRightToLeft() )
2823 if ( eAlign == TxtAlign::Left )
2824 eAlign = TxtAlign::Right;
2825 else if ( eAlign == TxtAlign::Right )
2826 eAlign = TxtAlign::Left;
2828 return eAlign;
2831 long TextEngine::ImpGetOutputOffset( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, sal_Int32 nIndex2 )
2833 TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
2835 sal_Int32 nPortionStart {0};
2836 const std::size_t nPortion = pPortion->GetTextPortions().FindPortion( nIndex, nPortionStart, true );
2838 TETextPortion* pTextPortion = pPortion->GetTextPortions()[ nPortion ];
2840 long nX;
2842 if ( ( nIndex == nPortionStart ) && ( nIndex == nIndex2 ) )
2844 // Output of full portion, so we need portion x offset.
2845 // Use ImpGetPortionXOffset, because GetXPos may deliver left or right position from portion, depending on R2L, L2R
2846 nX = ImpGetPortionXOffset( nPara, pLine, nPortion );
2847 if ( IsRightToLeft() )
2849 nX = -nX -pTextPortion->GetWidth();
2852 else
2854 nX = ImpGetXPos( nPara, pLine, nIndex, nIndex == nPortionStart );
2855 if ( nIndex2 != nIndex )
2857 const long nX2 = ImpGetXPos( nPara, pLine, nIndex2 );
2858 if ( ( !IsRightToLeft() && ( nX2 < nX ) ) ||
2859 ( IsRightToLeft() && ( nX2 > nX ) ) )
2861 nX = nX2;
2864 if ( IsRightToLeft() )
2866 nX = -nX;
2870 return nX;
2873 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */