1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
62 #include <string_view>
65 using namespace ::com::sun::star
;
66 using namespace ::com::sun::star::uno
;
68 TextEngine::TextEngine()
69 : mpActiveView
{nullptr}
70 , maTextColor
{COL_BLACK
}
77 , meAlign
{TxtAlign::Left
}
78 , mbIsFormatting
{false}
82 , mbUndoEnabled
{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
);
101 aFont
.SetTransparent( false );
102 Color
aFillColor( aFont
.GetFillColor() );
103 aFillColor
.SetTransparency( 0 );
104 aFont
.SetFillColor( aFillColor
);
108 TextEngine::~TextEngine()
112 mpIdleFormatter
.reset();
114 mpTEParaPortions
.reset();
115 mpViews
.reset(); // only the list, not the Views
116 mpRefDev
.disposeAndClear();
117 mpUndoManager
.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
)
159 mpActiveView
->HideSelection();
161 mpActiveView
= pTextView
;
164 mpActiveView
->ShowSelection();
168 void TextEngine::SetFont( const vcl::Font
& rFont
)
170 if ( rFont
== maFont
)
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
;
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(" ");
195 mnDefTab
= mpRefDev
->GetTextWidth("XXXX");
198 mnCharHeight
= mpRefDev
->GetTextHeight();
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
;
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;
237 pRet
= static_aLFText
;
240 pRet
= static_aCRText
;
243 pRet
= static_aCRLFText
;
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
) ) )
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
);
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
)
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
)
326 case KeyFuncType::UNDO
:
327 case KeyFuncType::REDO
:
328 case KeyFuncType::CUT
:
329 case KeyFuncType::PASTE
:
333 // might get handled below
334 eFunc
= KeyFuncType::DONTKNOW
;
337 if ( eFunc
== KeyFuncType::DONTKNOW
)
339 switch ( rKeyEvent
.GetKeyCode().GetCode() )
343 if ( !rKeyEvent
.GetKeyCode().IsMod2() )
348 if ( !rKeyEvent
.GetKeyCode().IsMod1() && !rKeyEvent
.GetKeyCode().IsMod2() )
352 bDoesChange
= TextEngine::IsSimpleCharInput( rKeyEvent
);
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()
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 );
382 ImpParagraphRemoved( TEXT_PARA_ALL
);
383 ImpParagraphInserted( 0 );
386 OUString
TextEngine::GetText( const TextSelection
& rSel
, LineEnd aSeparator
) const
388 if ( !rSel
.HasRange() )
391 TextSelection
aSel( rSel
);
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
)
413 return aText
.makeStringAndClear();
416 void TextEngine::ImpRemoveText()
420 const TextSelection aEmptySel
;
421 for (TextView
* pView
: *mpViews
)
423 pView
->ImpSetSelection( aEmptySel
);
428 void TextEngine::SetText( const OUString
& rText
)
432 const bool bUndoCurrentlyEnabled
= IsUndoEnabled();
433 // the manually inserted text cannot be reversed by the user
436 const TextSelection aEmptySel
;
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() )
451 if( rText
.isEmpty() ) // otherwise needs invalidation later; !bFormatted is sufficient
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
) )
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()
522 TextPaM
TextEngine::ImpDeleteText( const TextSelection
& rSel
)
524 if ( !rSel
.HasRange() )
525 return rSel
.GetStart();
527 TextSelection
aSel( rSel
);
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();
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();
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 );
575 aStartPaM
= ImpConnectParagraphs( nStartNode
, nEndNode
);
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();
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()
609 mxISC
= i18n::InputSequenceChecker::create( ::comphelper::getProcessComponentContext() );
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
;
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
;
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
] )
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
);
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 );
728 pPortion
->SetNotSimpleInvalid();
729 aPaM
= mpDoc
->InsertText( aPaM
, c
);
730 ImpCharsInserted( aPaM
.GetPara(), aPaM
.GetIndex()-1, 1 );
740 TextPaM
TextEngine::ImpInsertText( const TextSelection
& rCurSel
, const OUString
& rStr
)
746 if ( rCurSel
.HasRange() )
747 aPaM
= ImpDeleteText( rCurSel
);
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
);
758 nEnd
= aText
.getLength(); // do not dereference!
760 // Start == End => empty line
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
791 TextPaM
TextEngine::ImpInsertParaBreak( const TextSelection
& rCurSel
)
794 if ( rCurSel
.HasRange() )
795 aPaM
= ImpDeleteText( rCurSel
);
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
824 if ( bFirstParaContentChanged
)
825 Broadcast( TextHint( SfxHintId::TextParaContentChanged
, rPaM
.GetPara() ) );
830 tools::Rectangle
TextEngine::PaMtoEditCursor( const TextPaM
& rPaM
, bool bSpecial
)
832 SAL_WARN_IF( !GetUpdateMode(), "vcl", "PaMtoEditCursor: GetUpdateMode()" );
834 tools::Rectangle aEditCursor
;
837 if ( !mbHasMultiLineParas
)
839 nY
= rPaM
.GetPara() * mnCharHeight
;
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
);
856 tools::Rectangle
TextEngine::GetEditCursor( const TextPaM
& rPaM
, bool bSpecial
, bool bPreferPortionStart
)
858 if ( !IsFormatted() && !IsFormatting() )
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
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
) ) )
883 nCurIndex
= nCurIndex
+ rTmpLine
.GetLen();
888 // Cursor at end of paragraph
889 SAL_WARN_IF( rPaM
.GetIndex() != nCurIndex
, "vcl", "GetEditCursor: Bad Index!" );
891 pLine
= & ( pPortion
->GetLines().back() );
895 tools::Rectangle aEditCursor
;
897 aEditCursor
.SetTop( nY
);
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
);
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() ) )
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())
968 nX
+= nPortionTextWidth
- nPosInPortion
;
972 else // if ( nIndex == pLine->GetStart() )
974 if (pPortion
->GetKind() != PORTIONKIND_TAB
&& IsRightToLeft() != pPortion
->IsRightToLeft())
976 nX
+= nPortionTextWidth
;
983 const TextAttrib
* TextEngine::FindAttrib( const TextPaM
& rPaM
, sal_uInt16 nWhich
) const
985 const TextAttrib
* pAttr
= nullptr;
986 const TextCharAttrib
* pCharAttr
= FindCharAttrib( rPaM
, nWhich
);
988 pAttr
= &pCharAttr
->GetAttr();
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() );
1001 TextPaM
TextEngine::GetPaM( const Point
& rDocPos
)
1003 SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetPaM: GetUpdateMode()" );
1006 for ( sal_uInt32 nPortion
= 0; nPortion
< mpTEParaPortions
->Count(); ++nPortion
)
1008 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( nPortion
);
1009 long nTmpHeight
= pPortion
->GetLines().size() * mnCharHeight
;
1011 if ( nY
> rDocPos
.Y() )
1014 Point
aPosInPara( rDocPos
);
1015 aPosInPara
.AdjustY( -nY
);
1017 TextPaM
aPaM( nPortion
, 0 );
1018 aPaM
.GetIndex() = ImpFindIndex( nPortion
, aPosInPara
);
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;
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
];
1043 if ( nY
> rPosInPara
.Y() ) // that's it
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
);
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
)
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
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...
1098 nCurIndex
+= pTextPortion
->GetLen();
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
;
1142 long TextEngine::CalcTextWidth()
1144 if ( !IsFormatted() && !IsFormatting() )
1147 if ( 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()" );
1165 for ( auto nPortion
= mpTEParaPortions
->Count(); nPortion
; )
1166 nY
+= CalcParaHeight( --nPortion
);
1170 long TextEngine::CalcTextWidth( sal_uInt32 nPara
, sal_Int32 nPortionStart
, sal_Int32 nLen
)
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!" );
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
);
1186 void TextEngine::GetTextPortionRange(const TextPaM
& rPaM
, sal_Int32
& nStart
, sal_Int32
& nEnd
)
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();
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
);
1212 return pPPortion
->GetLines().size();
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();
1230 long TextEngine::CalcParaHeight( sal_uInt32 nParagraph
) const
1234 TEParaPortion
* pPPortion
= mpTEParaPortions
->GetObject( nParagraph
);
1235 SAL_WARN_IF( !pPPortion
, "vcl", "GetParaHeight: paragraph not found" );
1237 nHeight
= pPPortion
->GetLines().size() * mnCharHeight
;
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;
1248 for ( nLine
= 0; nLine
< nLines
; nLine
++ )
1250 TextLine
& rL
= pTEParaPortion
->GetLines()[ nLine
];
1251 if ( rL
.IsInvalid() )
1253 nFirstInvalid
= nLine
;
1258 for ( nLastInvalid
= nFirstInvalid
; nLastInvalid
< nLines
; nLastInvalid
++ )
1260 TextLine
& rL
= pTEParaPortion
->GetLines()[ nLine
];
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() )
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
)
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" );
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
)
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
)
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
);
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
);
1406 // pOut->SetTextLineColor( COL_LIGHTGRAY );
1411 void TextEngine::FormatAndUpdate( TextView
* pCurView
)
1417 IdleFormatAndUpdate( pCurView
);
1421 UpdateViews( pCurView
);
1425 void TextEngine::IdleFormatAndUpdate( TextView
* pCurView
, sal_uInt16 nMaxTimerRestarts
)
1427 mpIdleFormatter
->DoIdleFormat( pCurView
, nMaxTimerRestarts
);
1430 void TextEngine::TextModified()
1432 mbFormatted
= false;
1436 void TextEngine::UpdateViews( TextView
* pCurView
)
1438 if ( !GetUpdateMode() || IsFormatting() || maInvalidRect
.IsEmpty() )
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
);
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;
1492 void TextEngine::FormatDoc()
1494 if ( IsFormatted() || !GetUpdateMode() || IsFormatting() )
1497 mbIsFormatting
= true;
1498 mbHasMultiLineParas
= 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
) )
1516 // set InvalidRect only once
1517 if ( maInvalidRect
.IsEmpty() )
1519 // otherwise remains Empty() for Paperwidth 0 (AutoPageSize)
1520 const long nWidth
= 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() ) );
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;
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
;
1570 Broadcast( TextHint( SfxHintId::TextHeightChanged
) );
1574 mbIsFormatting
= false;
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
);
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
) );
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
) );
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
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
;
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;
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();
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!
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;
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
;
1821 std::unique_ptr
<TETextPortion
> pNewPortion(new TETextPortion( nNewChars
));
1822 pTEParaPortion
->GetTextPortions().insert( pTEParaPortion
->GetTextPortions().begin() + nNewPortionPos
, std::move(pNewPortion
) );
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;
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;
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!" );
1856 nPos
+= pTP
->GetLen();
1858 SAL_WARN_IF( !pTP
, "vcl", "RecalcTextPortion: Portion not found!" );
1859 if ( ( nPos
== nStartPos
) && ( (nPos
+pTP
->GetLen()) == nEnd
) )
1862 pTEParaPortion
->GetTextPortions().erase( pTEParaPortion
->GetTextPortions().begin() + nPortion
);
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() )
1879 if ( !IsFormatted() )
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() )
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
:
1939 SeekCursor( nPara
, nIndex
+1, aFont
, pOutDev
);
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
;
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
;
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
);
1998 pOutDev
->SetTextFillColor();
1999 aPos
.setX( rStartPos
.X() + ImpGetOutputOffset( nPara
, &rLine
, nTmpIndex
, nEnd
) );
2000 pOutDev
->DrawText( aPos
, pPortion
->GetNode()->GetText(), nTmpIndex
, nEnd
-nTmpIndex
);
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
);
2022 pOutDev
->Erase( aTabArea
);
2027 OSL_FAIL( "ImpPaint: Unknown Portion-Type !" );
2031 nIndex
+= pTextPortion
->GetLen();
2037 if ( pPaintArea
&& ( nY
>= pPaintArea
->Bottom() ) )
2038 break; // no more visible actions
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();
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
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;
2107 RecalcTextPortion( nPara
, nInvalidStart
, nInvalidDiff
);
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
)
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 ) ) )
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
);
2140 while ( nIndex
< pNode
->GetText().getLength() )
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() ) )
2171 pPortion
->GetWidth() = nXWidth
-1;
2172 nTmpWidth
= pPortion
->GetWidth();
2176 pPortion
->GetKind() = PORTIONKIND_TAB
;
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
;
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
;
2203 nTmpWidth
-= pPortion
->GetWidth();
2204 if ( pPortion
->GetKind() == PORTIONKIND_TAB
)
2213 pLine
->SetEnd( nPortionEnd
);
2214 OSL_ENSURE(pTEParaPortion
->GetTextPortions().size(),
2215 "CreateLines: No TextPortions?");
2216 pLine
->SetEndPortion( pTEParaPortion
->GetTextPortions().size() - 1 );
2221 pLine
->SetEnd( nPortionStart
);
2222 pLine
->SetEndPortion( nTmpPortion
-1 );
2224 else if ( bBrokenLine
)
2226 pLine
->SetEnd( nPortionStart
+1 );
2227 pLine
->SetEndPortion( nTmpPortion
-1 );
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
) )
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
;
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
) );
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
)
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() ) )
2288 pTEParaPortion
->CorrectValuesBehindLastFormattedLine( nLine
);
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
);
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
2314 if ( nLine
< pTEParaPortion
->GetLines().size()-1 )
2315 pLine
= &( pTEParaPortion
->GetLines()[ ++nLine
] );
2316 if ( pLine
&& ( nIndex
>= pNode
->GetText().getLength() ) )
2318 nDelFromLine
= nLine
;
2323 if ( nIndex
< pNode
->GetText().getLength() )
2326 pTEParaPortion
->GetLines().insert( pTEParaPortion
->GetLines().begin() + nLine
, TextLine() );
2327 pLine
= &pTEParaPortion
->GetLines()[nLine
];
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
)
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() );
2368 *pStartOfWord
= aSel
.GetStart();
2373 bool TextEngine::Read( SvStream
& rInput
, const TextSelection
* pSel
)
2375 const bool bUpdate
= GetUpdateMode();
2376 SetUpdateMode( false );
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
);
2393 bool bDone
= rInput
.ReadLine( aLine
);
2394 OUString
aTmpStr(OStringToOUString(aLine
, rInput
.GetStreamCharSet()));
2397 aSel
= ImpInsertText( aSel
, aTmpStr
);
2398 bDone
= rInput
.ReadLine( aLine
);
2399 aTmpStr
= OStringToOUString(aLine
, rInput
.GetStreamCharSet());
2401 aSel
= ImpInsertParaBreak( aSel
.GetEnd() );
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
)
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
)
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
)
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
)
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
)
2556 else if ( rPaM
.GetPara() == nPara
)
2558 rPaM
.GetIndex() = 0;
2559 if ( rPaM
.GetPara() >= nParas
)
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
)
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?
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
;
2672 void TextEngine::ImpInitWritingDirections( sal_uInt32 nPara
)
2674 TEParaPortion
* pParaPortion
= mpTEParaPortions
->GetObject( nPara
);
2675 std::vector
<TEWritingDirectionInfo
>& rInfos
= pParaPortion
->GetWritingDirectionInfos();
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
);
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
);
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
;
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();
2767 // Portions before must be removed, visual behind this portion
2768 nTmpPortion
= nTextPortion
;
2769 while ( nTmpPortion
> pLine
->GetStartPortion() )
2772 TETextPortion
* pPrevTextPortion
= pParaPortion
->GetTextPortions()[ nTmpPortion
];
2773 if ( pPrevTextPortion
->IsRightToLeft() && ( pPrevTextPortion
->GetKind() != PORTIONKIND_TAB
) )
2774 nX
-= pPrevTextPortion
->GetWidth();
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();
2792 // Portions before must be added, visual before this portion
2793 nTmpPortion
= nTextPortion
;
2794 while ( nTmpPortion
> pLine
->GetStartPortion() )
2797 TETextPortion
* pPrevTextPortion
= pParaPortion
->GetTextPortions()[ nTmpPortion
];
2798 if ( !pPrevTextPortion
->IsRightToLeft() && ( pPrevTextPortion
->GetKind() != PORTIONKIND_TAB
) )
2799 nX
-= pPrevTextPortion
->GetWidth();
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
;
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
];
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();
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
) ) )
2864 if ( IsRightToLeft() )
2873 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */