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/toolkit/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>
61 #include <o3tl/sorted_vector.hxx>
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
) );
93 mpRefDev
= VclPtr
<VirtualDevice
>::Create();
95 ImpInitLayoutMode( mpRefDev
);
99 vcl::Font
aFont(mpRefDev
->GetFont().GetFamilyName(), Size(0, 0));
100 aFont
.SetTransparent( false );
101 Color
aFillColor( aFont
.GetFillColor() );
102 aFillColor
.SetAlpha( 255 );
103 aFont
.SetFillColor( aFillColor
);
107 TextEngine::~TextEngine()
111 mpIdleFormatter
.reset();
113 mpTEParaPortions
.reset();
114 mpViews
.reset(); // only the list, not the Views
115 mpRefDev
.disposeAndClear();
116 mpUndoManager
.reset();
118 mpLocaleDataWrapper
.reset();
121 void TextEngine::InsertView( TextView
* pTextView
)
123 mpViews
->push_back( pTextView
);
124 pTextView
->SetSelection( TextSelection() );
126 if ( !GetActiveView() )
127 SetActiveView( pTextView
);
130 void TextEngine::RemoveView( TextView
* pTextView
)
132 TextViews::iterator it
= std::find( mpViews
->begin(), mpViews
->end(), pTextView
);
133 if( it
!= mpViews
->end() )
135 pTextView
->HideCursor();
136 mpViews
->erase( it
);
137 if ( pTextView
== GetActiveView() )
138 SetActiveView( nullptr );
142 sal_uInt16
TextEngine::GetViewCount() const
144 return mpViews
->size();
147 TextView
* TextEngine::GetView( sal_uInt16 nView
) const
149 return (*mpViews
)[ nView
];
153 void TextEngine::SetActiveView( TextView
* pTextView
)
155 if ( pTextView
!= mpActiveView
)
158 mpActiveView
->HideSelection();
160 mpActiveView
= pTextView
;
163 mpActiveView
->ShowSelection();
167 void TextEngine::SetFont( const vcl::Font
& rFont
)
169 if ( rFont
== maFont
)
173 // #i40221# As the font's color now defaults to transparent (since i35764)
174 // we have to choose a useful textcolor in this case.
175 // Otherwise maTextColor and maFont.GetColor() are both transparent...
176 if( rFont
.GetColor() == COL_TRANSPARENT
)
177 maTextColor
= COL_BLACK
;
179 maTextColor
= rFont
.GetColor();
181 // Do not allow transparent fonts because of selection
182 // (otherwise delete the background in ImplPaint later differently)
183 maFont
.SetTransparent( false );
184 // Tell VCL not to use the font color, use text color from OutputDevice
185 maFont
.SetColor( COL_TRANSPARENT
);
186 Color
aFillColor( maFont
.GetFillColor() );
187 aFillColor
.SetAlpha( 255 );
188 maFont
.SetFillColor( aFillColor
);
190 maFont
.SetAlignment( ALIGN_TOP
);
191 mpRefDev
->SetFont( maFont
);
192 mnDefTab
= mpRefDev
->GetTextWidth(" ");
194 mnDefTab
= mpRefDev
->GetTextWidth("XXXX");
197 mnCharHeight
= mpRefDev
->GetTextHeight();
202 for ( auto nView
= mpViews
->size(); nView
; )
204 TextView
* pView
= (*mpViews
)[ --nView
];
205 pView
->GetWindow()->SetInputContext( InputContext( GetFont(), !pView
->IsReadOnly() ? InputContextFlags::Text
|InputContextFlags::ExtText
: InputContextFlags::NONE
) );
210 void TextEngine::SetMaxTextLen( sal_Int32 nLen
)
212 mnMaxTextLen
= nLen
>=0 ? nLen
: EDIT_NOLIMIT
;
215 void TextEngine::SetMaxTextWidth( tools::Long nMaxWidth
)
217 if ( nMaxWidth
>=0 && nMaxWidth
!= mnMaxTextWidth
)
219 mnMaxTextWidth
= nMaxWidth
;
225 const sal_Unicode static_aLFText
[] = { '\n', 0 };
226 const sal_Unicode static_aCRText
[] = { '\r', 0 };
227 const sal_Unicode static_aCRLFText
[] = { '\r', '\n', 0 };
229 static const sal_Unicode
* static_getLineEndText( LineEnd aLineEnd
)
231 const sal_Unicode
* pRet
= nullptr;
236 pRet
= static_aLFText
;
239 pRet
= static_aCRText
;
242 pRet
= static_aCRLFText
;
248 void TextEngine::ReplaceText(const TextSelection
& rSel
, const OUString
& rText
)
250 ImpInsertText( rSel
, rText
);
253 OUString
TextEngine::GetText( LineEnd aSeparator
) const
255 return mpDoc
->GetText( static_getLineEndText( aSeparator
) );
258 OUString
TextEngine::GetTextLines( LineEnd aSeparator
) const
260 OUStringBuffer aText
;
261 const sal_uInt32 nParas
= mpTEParaPortions
->Count();
262 const sal_Unicode
* pSep
= static_getLineEndText( aSeparator
);
263 for ( sal_uInt32 nP
= 0; nP
< nParas
; ++nP
)
265 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nP
);
267 const size_t nLines
= pTEParaPortion
->GetLines().size();
268 for ( size_t nL
= 0; nL
< nLines
; ++nL
)
270 TextLine
& rLine
= pTEParaPortion
->GetLines()[nL
];
271 aText
.append( pTEParaPortion
->GetNode()->GetText().subView(rLine
.GetStart(), rLine
.GetEnd() - rLine
.GetStart()) );
272 if ( pSep
&& ( ( (nP
+1) < nParas
) || ( (nL
+1) < nLines
) ) )
276 return aText
.makeStringAndClear();
279 OUString
TextEngine::GetText( sal_uInt32 nPara
) const
281 return mpDoc
->GetText( nPara
);
284 sal_Int32
TextEngine::GetTextLen() const
286 return mpDoc
->GetTextLen( static_getLineEndText( LINEEND_LF
) );
289 sal_Int32
TextEngine::GetTextLen( const TextSelection
& rSel
) const
291 TextSelection
aSel( rSel
);
293 ValidateSelection( aSel
);
294 return mpDoc
->GetTextLen( static_getLineEndText( LINEEND_LF
), &aSel
);
297 sal_Int32
TextEngine::GetTextLen( const sal_uInt32 nPara
) const
299 return mpDoc
->GetNodes()[ nPara
]->GetText().getLength();
302 void TextEngine::SetUpdateMode( bool bUpdate
)
304 if ( bUpdate
!= mbUpdate
)
309 FormatAndUpdate( GetActiveView() );
310 if ( GetActiveView() )
311 GetActiveView()->ShowCursor();
316 bool TextEngine::DoesKeyChangeText( const KeyEvent
& rKeyEvent
)
318 bool bDoesChange
= false;
320 KeyFuncType eFunc
= rKeyEvent
.GetKeyCode().GetFunction();
321 if ( eFunc
!= KeyFuncType::DONTKNOW
)
325 case KeyFuncType::UNDO
:
326 case KeyFuncType::REDO
:
327 case KeyFuncType::CUT
:
328 case KeyFuncType::PASTE
:
332 // might get handled below
333 eFunc
= KeyFuncType::DONTKNOW
;
336 if ( eFunc
== KeyFuncType::DONTKNOW
)
338 switch ( rKeyEvent
.GetKeyCode().GetCode() )
342 if ( !rKeyEvent
.GetKeyCode().IsMod2() )
347 if ( !rKeyEvent
.GetKeyCode().IsMod1() && !rKeyEvent
.GetKeyCode().IsMod2() )
351 bDoesChange
= TextEngine::IsSimpleCharInput( rKeyEvent
);
357 bool TextEngine::IsSimpleCharInput( const KeyEvent
& rKeyEvent
)
359 return rKeyEvent
.GetCharCode() >= 32 && rKeyEvent
.GetCharCode() != 127 &&
360 KEY_MOD1
!= (rKeyEvent
.GetKeyCode().GetModifier() & ~KEY_SHIFT
) && // (ssa) #i45714#:
361 KEY_MOD2
!= (rKeyEvent
.GetKeyCode().GetModifier() & ~KEY_SHIFT
); // check for Ctrl and Alt separately
364 void TextEngine::ImpInitDoc()
369 mpDoc
.reset( new TextDoc
);
371 mpTEParaPortions
.reset(new TEParaPortions
);
373 std::unique_ptr
<TextNode
> pNode(new TextNode( OUString() ));
374 mpDoc
->GetNodes().insert( mpDoc
->GetNodes().begin(), std::move(pNode
) );
376 TEParaPortion
* pIniPortion
= new TEParaPortion( mpDoc
->GetNodes().begin()->get() );
377 mpTEParaPortions
->Insert( pIniPortion
, 0 );
381 ImpParagraphRemoved( TEXT_PARA_ALL
);
382 ImpParagraphInserted( 0 );
385 OUString
TextEngine::GetText( const TextSelection
& rSel
, LineEnd aSeparator
) const
387 if ( !rSel
.HasRange() )
390 TextSelection
aSel( rSel
);
393 OUStringBuffer aText
;
394 const sal_uInt32 nStartPara
= aSel
.GetStart().GetPara();
395 const sal_uInt32 nEndPara
= aSel
.GetEnd().GetPara();
396 const sal_Unicode
* pSep
= static_getLineEndText( aSeparator
);
397 for ( sal_uInt32 nNode
= aSel
.GetStart().GetPara(); nNode
<= nEndPara
; ++nNode
)
399 TextNode
* pNode
= mpDoc
->GetNodes()[ nNode
].get();
401 sal_Int32 nStartPos
= 0;
402 sal_Int32 nEndPos
= pNode
->GetText().getLength();
403 if ( nNode
== nStartPara
)
404 nStartPos
= aSel
.GetStart().GetIndex();
405 if ( nNode
== nEndPara
) // may also be == nStart!
406 nEndPos
= aSel
.GetEnd().GetIndex();
408 aText
.append(pNode
->GetText().subView(nStartPos
, nEndPos
-nStartPos
));
409 if ( nNode
< nEndPara
)
412 return aText
.makeStringAndClear();
415 void TextEngine::ImpRemoveText()
419 const TextSelection aEmptySel
;
420 for (TextView
* pView
: *mpViews
)
422 pView
->ImpSetSelection( aEmptySel
);
427 void TextEngine::SetText( const OUString
& rText
)
431 const bool bUndoCurrentlyEnabled
= IsUndoEnabled();
432 // the manually inserted text cannot be reversed by the user
435 const TextSelection aEmptySel
;
438 if ( !rText
.isEmpty() )
439 aPaM
= ImpInsertText( aEmptySel
, rText
);
441 for (TextView
* pView
: *mpViews
)
443 pView
->ImpSetSelection( aEmptySel
);
445 // if no text, then no Format&Update => the text remains
446 if ( rText
.isEmpty() && GetUpdateMode() )
450 if( rText
.isEmpty() ) // otherwise needs invalidation later; !bFormatted is sufficient
455 EnableUndo( bUndoCurrentlyEnabled
);
456 SAL_WARN_IF( HasUndoManager() && GetUndoManager().GetUndoActionCount(), "vcl", "SetText: Undo!" );
459 void TextEngine::CursorMoved( sal_uInt32 nNode
)
461 // delete empty attribute; but only if paragraph is not empty!
462 TextNode
* pNode
= mpDoc
->GetNodes()[ nNode
].get();
463 if ( pNode
&& pNode
->GetCharAttribs().HasEmptyAttribs() && !pNode
->GetText().isEmpty() )
464 pNode
->GetCharAttribs().DeleteEmptyAttribs();
467 void TextEngine::ImpRemoveChars( const TextPaM
& rPaM
, sal_Int32 nChars
)
469 SAL_WARN_IF( !nChars
, "vcl", "ImpRemoveChars: 0 Chars?!" );
470 if ( IsUndoEnabled() && !IsInUndo() )
472 // attributes have to be saved for UNDO before RemoveChars!
473 TextNode
* pNode
= mpDoc
->GetNodes()[ rPaM
.GetPara() ].get();
474 OUString
aStr( pNode
->GetText().copy( rPaM
.GetIndex(), nChars
) );
476 // check if attributes are being deleted or changed
477 const sal_Int32 nStart
= rPaM
.GetIndex();
478 const sal_Int32 nEnd
= nStart
+ nChars
;
479 for ( sal_uInt16 nAttr
= pNode
->GetCharAttribs().Count(); nAttr
; )
481 TextCharAttrib
& rAttr
= pNode
->GetCharAttribs().GetAttrib( --nAttr
);
482 if ( ( rAttr
.GetEnd() >= nStart
) && ( rAttr
.GetStart() < nEnd
) )
487 InsertUndo( std::make_unique
<TextUndoRemoveChars
>( this, rPaM
, aStr
) );
490 mpDoc
->RemoveChars( rPaM
, nChars
);
491 ImpCharsRemoved( rPaM
.GetPara(), rPaM
.GetIndex(), nChars
);
494 TextPaM
TextEngine::ImpConnectParagraphs( sal_uInt32 nLeft
, sal_uInt32 nRight
)
496 SAL_WARN_IF( nLeft
== nRight
, "vcl", "ImpConnectParagraphs: connect the very same paragraph ?" );
498 TextNode
* pLeft
= mpDoc
->GetNodes()[ nLeft
].get();
499 TextNode
* pRight
= mpDoc
->GetNodes()[ nRight
].get();
501 if ( IsUndoEnabled() && !IsInUndo() )
502 InsertUndo( std::make_unique
<TextUndoConnectParas
>( this, nLeft
, pLeft
->GetText().getLength() ) );
504 // first lookup Portions, as pRight is gone after ConnectParagraphs
505 TEParaPortion
* pLeftPortion
= mpTEParaPortions
->GetObject( nLeft
);
506 TEParaPortion
* pRightPortion
= mpTEParaPortions
->GetObject( nRight
);
507 SAL_WARN_IF( !pLeft
|| !pLeftPortion
, "vcl", "ImpConnectParagraphs(1): Hidden Portion" );
508 SAL_WARN_IF( !pRight
|| !pRightPortion
, "vcl", "ImpConnectParagraphs(2): Hidden Portion" );
510 TextPaM aPaM
= mpDoc
->ConnectParagraphs( pLeft
, pRight
);
511 ImpParagraphRemoved( nRight
);
513 pLeftPortion
->MarkSelectionInvalid( aPaM
.GetIndex() );
515 mpTEParaPortions
->Remove( nRight
);
516 // the right Node is deleted by EditDoc::ConnectParagraphs()
521 TextPaM
TextEngine::ImpDeleteText( const TextSelection
& rSel
)
523 if ( !rSel
.HasRange() )
524 return rSel
.GetStart();
526 TextSelection
aSel( rSel
);
528 TextPaM
aStartPaM( aSel
.GetStart() );
529 TextPaM
aEndPaM( aSel
.GetEnd() );
531 CursorMoved( aStartPaM
.GetPara() ); // so that newly-adjusted attributes vanish
532 CursorMoved( aEndPaM
.GetPara() ); // so that newly-adjusted attributes vanish
534 SAL_WARN_IF( !mpDoc
->IsValidPaM( aStartPaM
), "vcl", "ImpDeleteText(1): bad Index" );
535 SAL_WARN_IF( !mpDoc
->IsValidPaM( aEndPaM
), "vcl", "ImpDeleteText(2): bad Index" );
537 const sal_uInt32 nStartNode
= aStartPaM
.GetPara();
538 sal_uInt32 nEndNode
= aEndPaM
.GetPara();
540 // remove all Nodes inbetween
541 for ( sal_uInt32 z
= nStartNode
+1; z
< nEndNode
; ++z
)
543 // always nStartNode+1, because of Remove()!
544 ImpRemoveParagraph( nStartNode
+1 );
547 if ( nStartNode
!= nEndNode
)
549 // the remainder of StartNodes...
550 TextNode
* pLeft
= mpDoc
->GetNodes()[ nStartNode
].get();
551 sal_Int32 nChars
= pLeft
->GetText().getLength() - aStartPaM
.GetIndex();
554 ImpRemoveChars( aStartPaM
, nChars
);
555 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( nStartNode
);
556 SAL_WARN_IF( !pPortion
, "vcl", "ImpDeleteText(3): bad Index" );
557 pPortion
->MarkSelectionInvalid( aStartPaM
.GetIndex() );
560 // the beginning of EndNodes...
561 nEndNode
= nStartNode
+1; // the other paragraphs were deleted
562 nChars
= aEndPaM
.GetIndex();
565 aEndPaM
.GetPara() = nEndNode
;
566 aEndPaM
.GetIndex() = 0;
567 ImpRemoveChars( aEndPaM
, nChars
);
568 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( nEndNode
);
569 SAL_WARN_IF( !pPortion
, "vcl", "ImpDeleteText(4): bad Index" );
570 pPortion
->MarkSelectionInvalid( 0 );
574 aStartPaM
= ImpConnectParagraphs( nStartNode
, nEndNode
);
578 const sal_Int32 nChars
= aEndPaM
.GetIndex() - aStartPaM
.GetIndex();
579 ImpRemoveChars( aStartPaM
, nChars
);
580 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( nStartNode
);
581 SAL_WARN_IF( !pPortion
, "vcl", "ImpDeleteText(5): bad Index" );
582 pPortion
->MarkInvalid( aEndPaM
.GetIndex(), aStartPaM
.GetIndex() - aEndPaM
.GetIndex() );
585 // UpdateSelections();
590 void TextEngine::ImpRemoveParagraph( sal_uInt32 nPara
)
592 std::unique_ptr
<TextNode
> pNode
= std::move(mpDoc
->GetNodes()[ nPara
]);
594 // the Node is handled by Undo and is deleted if appropriate
595 mpDoc
->GetNodes().erase( mpDoc
->GetNodes().begin() + nPara
);
596 if ( IsUndoEnabled() && !IsInUndo() )
597 InsertUndo( std::make_unique
<TextUndoDelPara
>( this, pNode
.release(), nPara
) );
599 mpTEParaPortions
->Remove( nPara
);
601 ImpParagraphRemoved( nPara
);
604 uno::Reference
< i18n::XExtendedInputSequenceChecker
> const & TextEngine::GetInputSequenceChecker()
608 mxISC
= i18n::InputSequenceChecker::create( ::comphelper::getProcessComponentContext() );
613 bool TextEngine::IsInputSequenceCheckingRequired( sal_Unicode c
, const TextSelection
& rCurSel
) const
615 // get the index that really is first
616 const sal_Int32 nFirstPos
= std::min(rCurSel
.GetStart().GetIndex(), rCurSel
.GetEnd().GetIndex());
618 bool bIsSequenceChecking
=
619 SvtCTLOptions::IsCTLFontEnabled() &&
620 SvtCTLOptions::IsCTLSequenceChecking() &&
621 nFirstPos
!= 0; /* first char needs not to be checked */
623 if (bIsSequenceChecking
)
625 uno::Reference
< i18n::XBreakIterator
> xBI
= const_cast<TextEngine
*>(this)->GetBreakIterator();
626 bIsSequenceChecking
= xBI
.is() && i18n::ScriptType::COMPLEX
== xBI
->getScriptType( OUString( c
), 0 );
629 return bIsSequenceChecking
;
632 TextPaM
TextEngine::ImpInsertText( const TextSelection
& rCurSel
, sal_Unicode c
, bool bOverwrite
)
634 return ImpInsertText( c
, rCurSel
, bOverwrite
);
637 TextPaM
TextEngine::ImpInsertText( sal_Unicode c
, const TextSelection
& rCurSel
, bool bOverwrite
, bool bIsUserInput
)
639 SAL_WARN_IF( c
== '\n', "vcl", "InsertText: NewLine!" );
640 SAL_WARN_IF( c
== '\r', "vcl", "InsertText: NewLine!" );
642 TextPaM
aPaM( rCurSel
.GetStart() );
643 TextNode
* pNode
= mpDoc
->GetNodes()[ aPaM
.GetPara() ].get();
645 bool bDoOverwrite
= bOverwrite
&& ( aPaM
.GetIndex() < pNode
->GetText().getLength() );
647 bool bUndoAction
= rCurSel
.HasRange() || bDoOverwrite
;
652 if ( rCurSel
.HasRange() )
654 aPaM
= ImpDeleteText( rCurSel
);
656 else if ( bDoOverwrite
)
658 // if selection, then don't overwrite a character
659 TextSelection
aTmpSel( aPaM
);
660 ++aTmpSel
.GetEnd().GetIndex();
661 ImpDeleteText( aTmpSel
);
664 if (bIsUserInput
&& IsInputSequenceCheckingRequired( c
, rCurSel
))
666 uno::Reference
< i18n::XExtendedInputSequenceChecker
> xISC
= GetInputSequenceChecker();
670 sal_Int32 nTmpPos
= aPaM
.GetIndex();
671 sal_Int16 nCheckMode
= SvtCTLOptions::IsCTLSequenceCheckingRestricted() ?
672 i18n::InputSequenceCheckMode::STRICT
: i18n::InputSequenceCheckMode::BASIC
;
674 // the text that needs to be checked is only the one
675 // before the current cursor position
676 OUString
aOldText( mpDoc
->GetText( aPaM
.GetPara() ).copy(0, nTmpPos
) );
677 if (SvtCTLOptions::IsCTLSequenceCheckingTypeAndReplace())
679 OUString
aNewText( aOldText
);
680 xISC
->correctInputSequence( aNewText
, nTmpPos
- 1, c
, nCheckMode
);
682 // find position of first character that has changed
683 const sal_Int32 nOldLen
= aOldText
.getLength();
684 const sal_Int32 nNewLen
= aNewText
.getLength();
685 const sal_Unicode
*pOldTxt
= aOldText
.getStr();
686 const sal_Unicode
*pNewTxt
= aNewText
.getStr();
687 sal_Int32 nChgPos
= 0;
688 while ( nChgPos
< nOldLen
&& nChgPos
< nNewLen
&&
689 pOldTxt
[nChgPos
] == pNewTxt
[nChgPos
] )
692 OUString
aChgText( aNewText
.copy( nChgPos
) );
694 // select text from first pos to be changed to current pos
695 TextSelection
aSel( TextPaM( aPaM
.GetPara(), nChgPos
), aPaM
);
697 if (!aChgText
.isEmpty())
698 // ImpInsertText implicitly handles undo...
699 return ImpInsertText( aSel
, aChgText
);
705 // should the character be ignored (i.e. not get inserted) ?
706 if (!xISC
->checkInputSequence( aOldText
, nTmpPos
- 1, c
, nCheckMode
))
707 return aPaM
; // nothing to be done -> no need for undo
711 // at this point now we will insert the character 'normally' some lines below...
714 if ( IsUndoEnabled() && !IsInUndo() )
716 std::unique_ptr
<TextUndoInsertChars
> pNewUndo(new TextUndoInsertChars( this, aPaM
, OUString(c
) ));
717 bool bTryMerge
= !bDoOverwrite
&& ( c
!= ' ' );
718 InsertUndo( std::move(pNewUndo
), bTryMerge
);
721 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( aPaM
.GetPara() );
722 pPortion
->MarkInvalid( aPaM
.GetIndex(), 1 );
724 pPortion
->SetNotSimpleInvalid();
725 aPaM
= mpDoc
->InsertText( aPaM
, c
);
726 ImpCharsInserted( aPaM
.GetPara(), aPaM
.GetIndex()-1, 1 );
736 TextPaM
TextEngine::ImpInsertText( const TextSelection
& rCurSel
, const OUString
& rStr
)
742 if ( rCurSel
.HasRange() )
743 aPaM
= ImpDeleteText( rCurSel
);
745 aPaM
= rCurSel
.GetEnd();
747 OUString
aText(convertLineEnd(rStr
, LINEEND_LF
));
749 sal_Int32 nStart
= 0;
750 while ( nStart
< aText
.getLength() )
752 sal_Int32 nEnd
= aText
.indexOf( LINE_SEP
, nStart
);
754 nEnd
= aText
.getLength(); // do not dereference!
756 // Start == End => empty line
759 OUString
aLine(aText
.copy(nStart
, nEnd
-nStart
));
760 if ( IsUndoEnabled() && !IsInUndo() )
761 InsertUndo( std::make_unique
<TextUndoInsertChars
>( this, aPaM
, aLine
) );
763 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( aPaM
.GetPara() );
764 pPortion
->MarkInvalid( aPaM
.GetIndex(), aLine
.getLength() );
765 if (aLine
.indexOf( '\t' ) != -1)
766 pPortion
->SetNotSimpleInvalid();
768 aPaM
= mpDoc
->InsertText( aPaM
, aLine
);
769 ImpCharsInserted( aPaM
.GetPara(), aPaM
.GetIndex()-aLine
.getLength(), aLine
.getLength() );
772 if ( nEnd
< aText
.getLength() )
773 aPaM
= ImpInsertParaBreak( aPaM
);
775 if ( nEnd
== aText
.getLength() ) // #108611# prevent overflow in "nStart = nEnd+1" calculation
787 TextPaM
TextEngine::ImpInsertParaBreak( const TextSelection
& rCurSel
)
790 if ( rCurSel
.HasRange() )
791 aPaM
= ImpDeleteText( rCurSel
);
793 aPaM
= rCurSel
.GetEnd();
795 return ImpInsertParaBreak( aPaM
);
798 TextPaM
TextEngine::ImpInsertParaBreak( const TextPaM
& rPaM
)
800 if ( IsUndoEnabled() && !IsInUndo() )
801 InsertUndo( std::make_unique
<TextUndoSplitPara
>( this, rPaM
.GetPara(), rPaM
.GetIndex() ) );
803 TextNode
* pNode
= mpDoc
->GetNodes()[ rPaM
.GetPara() ].get();
804 bool bFirstParaContentChanged
= rPaM
.GetIndex() < pNode
->GetText().getLength();
806 TextPaM
aPaM( mpDoc
->InsertParaBreak( rPaM
) );
808 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( rPaM
.GetPara() );
809 SAL_WARN_IF( !pPortion
, "vcl", "ImpInsertParaBreak: Hidden Portion" );
810 pPortion
->MarkInvalid( rPaM
.GetIndex(), 0 );
812 TextNode
* pNewNode
= mpDoc
->GetNodes()[ aPaM
.GetPara() ].get();
813 TEParaPortion
* pNewPortion
= new TEParaPortion( pNewNode
);
814 mpTEParaPortions
->Insert( pNewPortion
, aPaM
.GetPara() );
815 ImpParagraphInserted( aPaM
.GetPara() );
817 CursorMoved( rPaM
.GetPara() ); // if empty attribute created
820 if ( bFirstParaContentChanged
)
821 Broadcast( TextHint( SfxHintId::TextParaContentChanged
, rPaM
.GetPara() ) );
826 tools::Rectangle
TextEngine::PaMtoEditCursor( const TextPaM
& rPaM
, bool bSpecial
)
828 SAL_WARN_IF( !GetUpdateMode(), "vcl", "PaMtoEditCursor: GetUpdateMode()" );
830 tools::Rectangle aEditCursor
;
833 if ( !mbHasMultiLineParas
)
835 nY
= rPaM
.GetPara() * mnCharHeight
;
839 for ( sal_uInt32 nPortion
= 0; nPortion
< rPaM
.GetPara(); ++nPortion
)
841 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject(nPortion
);
842 nY
+= pPortion
->GetLines().size() * mnCharHeight
;
846 aEditCursor
= GetEditCursor( rPaM
, bSpecial
);
847 aEditCursor
.AdjustTop(nY
);
848 aEditCursor
.AdjustBottom(nY
);
852 tools::Rectangle
TextEngine::GetEditCursor( const TextPaM
& rPaM
, bool bSpecial
, bool bPreferPortionStart
)
854 if ( !IsFormatted() && !IsFormatting() )
857 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( rPaM
.GetPara() );
858 //TextNode* pNode = mpDoc->GetNodes().GetObject( rPaM.GetPara() );
861 bSpecial: If behind the last character of a made up line, stay at the
862 end of the line, not at the start of the next line.
863 Purpose: - really END = > behind the last character
869 sal_Int32 nCurIndex
= 0;
870 TextLine
* pLine
= nullptr;
871 for (TextLine
& rTmpLine
: pPortion
->GetLines())
873 if ( ( rTmpLine
.GetStart() == rPaM
.GetIndex() ) || ( rTmpLine
.IsIn( rPaM
.GetIndex(), bSpecial
) ) )
879 nCurIndex
= nCurIndex
+ rTmpLine
.GetLen();
884 // Cursor at end of paragraph
885 SAL_WARN_IF( rPaM
.GetIndex() != nCurIndex
, "vcl", "GetEditCursor: Bad Index!" );
887 pLine
= & ( pPortion
->GetLines().back() );
891 tools::Rectangle aEditCursor
;
893 aEditCursor
.SetTop( nY
);
895 aEditCursor
.SetBottom( nY
-1 );
897 // search within the line
898 tools::Long nX
= ImpGetXPos( rPaM
.GetPara(), pLine
, rPaM
.GetIndex(), bPreferPortionStart
);
899 aEditCursor
.SetLeft(nX
);
900 aEditCursor
.SetRight(nX
);
904 tools::Long
TextEngine::ImpGetXPos( sal_uInt32 nPara
, TextLine
* pLine
, sal_Int32 nIndex
, bool bPreferPortionStart
)
906 SAL_WARN_IF( ( nIndex
< pLine
->GetStart() ) || ( nIndex
> pLine
->GetEnd() ) , "vcl", "ImpGetXPos: Bad parameters!" );
908 bool bDoPreferPortionStart
= bPreferPortionStart
;
909 // Assure that the portion belongs to this line
910 if ( nIndex
== pLine
->GetStart() )
911 bDoPreferPortionStart
= true;
912 else if ( nIndex
== pLine
->GetEnd() )
913 bDoPreferPortionStart
= false;
915 TEParaPortion
* pParaPortion
= mpTEParaPortions
->GetObject( nPara
);
917 sal_Int32 nTextPortionStart
= 0;
918 std::size_t nTextPortion
= pParaPortion
->GetTextPortions().FindPortion( nIndex
, nTextPortionStart
, bDoPreferPortionStart
);
920 SAL_WARN_IF( ( nTextPortion
< pLine
->GetStartPortion() ) || ( nTextPortion
> pLine
->GetEndPortion() ), "vcl", "GetXPos: Portion not in current line!" );
922 TETextPortion
& rPortion
= pParaPortion
->GetTextPortions()[ nTextPortion
];
924 tools::Long nX
= ImpGetPortionXOffset( nPara
, pLine
, nTextPortion
);
926 tools::Long nPortionTextWidth
= rPortion
.GetWidth();
928 if ( nTextPortionStart
!= nIndex
)
930 // Search within portion...
931 if ( nIndex
== ( nTextPortionStart
+ rPortion
.GetLen() ) )
934 if ( ( rPortion
.GetKind() == PORTIONKIND_TAB
) ||
935 ( !IsRightToLeft() && !rPortion
.IsRightToLeft() ) ||
936 ( IsRightToLeft() && rPortion
.IsRightToLeft() ) )
938 nX
+= nPortionTextWidth
;
939 if ( ( rPortion
.GetKind() == PORTIONKIND_TAB
) && ( (nTextPortion
+1) < pParaPortion
->GetTextPortions().size() ) )
941 TETextPortion
& rNextPortion
= pParaPortion
->GetTextPortions()[ nTextPortion
+1 ];
942 if (rNextPortion
.GetKind() != PORTIONKIND_TAB
&& IsRightToLeft() != rNextPortion
.IsRightToLeft())
944 // End of the tab portion, use start of next for cursor pos
945 SAL_WARN_IF( bPreferPortionStart
, "vcl", "ImpGetXPos: How can we get here!" );
946 nX
= ImpGetXPos( nPara
, pLine
, nIndex
, true );
952 else if ( rPortion
.GetKind() == PORTIONKIND_TEXT
)
954 SAL_WARN_IF( nIndex
== pLine
->GetStart(), "vcl", "ImpGetXPos: Strange behavior" );
956 tools::Long nPosInPortion
= CalcTextWidth( nPara
, nTextPortionStart
, nIndex
-nTextPortionStart
);
958 if (IsRightToLeft() == rPortion
.IsRightToLeft())
964 nX
+= nPortionTextWidth
- nPosInPortion
;
968 else // if ( nIndex == pLine->GetStart() )
970 if (rPortion
.GetKind() != PORTIONKIND_TAB
&& IsRightToLeft() != rPortion
.IsRightToLeft())
972 nX
+= nPortionTextWidth
;
979 const TextAttrib
* TextEngine::FindAttrib( const TextPaM
& rPaM
, sal_uInt16 nWhich
) const
981 const TextAttrib
* pAttr
= nullptr;
982 const TextCharAttrib
* pCharAttr
= FindCharAttrib( rPaM
, nWhich
);
984 pAttr
= &pCharAttr
->GetAttr();
988 const TextCharAttrib
* TextEngine::FindCharAttrib( const TextPaM
& rPaM
, sal_uInt16 nWhich
) const
990 const TextCharAttrib
* pAttr
= nullptr;
991 TextNode
* pNode
= mpDoc
->GetNodes()[ rPaM
.GetPara() ].get();
992 if (pNode
&& (rPaM
.GetIndex() <= pNode
->GetText().getLength()))
993 pAttr
= pNode
->GetCharAttribs().FindAttrib( nWhich
, rPaM
.GetIndex() );
997 TextPaM
TextEngine::GetPaM( const Point
& rDocPos
)
999 SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetPaM: GetUpdateMode()" );
1002 for ( sal_uInt32 nPortion
= 0; nPortion
< mpTEParaPortions
->Count(); ++nPortion
)
1004 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( nPortion
);
1005 tools::Long nTmpHeight
= pPortion
->GetLines().size() * mnCharHeight
;
1007 if ( nY
> rDocPos
.Y() )
1010 Point
aPosInPara( rDocPos
);
1011 aPosInPara
.AdjustY( -nY
);
1013 TextPaM
aPaM( nPortion
, 0 );
1014 aPaM
.GetIndex() = ImpFindIndex( nPortion
, aPosInPara
);
1019 // not found - go to last visible
1020 const sal_uInt32 nLastNode
= static_cast<sal_uInt32
>(mpDoc
->GetNodes().size() - 1);
1021 TextNode
* pLast
= mpDoc
->GetNodes()[ nLastNode
].get();
1022 return TextPaM( nLastNode
, pLast
->GetText().getLength() );
1025 sal_Int32
TextEngine::ImpFindIndex( sal_uInt32 nPortion
, const Point
& rPosInPara
)
1027 SAL_WARN_IF( !IsFormatted(), "vcl", "GetPaM: Not formatted" );
1028 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( nPortion
);
1030 sal_Int32 nCurIndex
= 0;
1033 TextLine
* pLine
= nullptr;
1034 std::vector
<TextLine
>::size_type nLine
;
1035 for ( nLine
= 0; nLine
< pPortion
->GetLines().size(); nLine
++ )
1037 TextLine
& rmpLine
= pPortion
->GetLines()[ nLine
];
1039 if ( nY
> rPosInPara
.Y() ) // that's it
1042 break; // correct Y-Position not needed
1046 assert(pLine
&& "ImpFindIndex: pLine ?");
1048 nCurIndex
= GetCharPos( nPortion
, nLine
, rPosInPara
.X() );
1050 if ( nCurIndex
&& ( nCurIndex
== pLine
->GetEnd() ) &&
1051 ( pLine
!= &( pPortion
->GetLines().back() ) ) )
1053 uno::Reference
< i18n::XBreakIterator
> xBI
= GetBreakIterator();
1054 sal_Int32 nCount
= 1;
1055 nCurIndex
= xBI
->previousCharacters( pPortion
->GetNode()->GetText(), nCurIndex
, GetLocale(), i18n::CharacterIteratorMode::SKIPCELL
, nCount
, nCount
);
1060 sal_Int32
TextEngine::GetCharPos( sal_uInt32 nPortion
, std::vector
<TextLine
>::size_type nLine
, tools::Long nXPos
)
1063 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( nPortion
);
1064 TextLine
& rLine
= pPortion
->GetLines()[ nLine
];
1066 sal_Int32 nCurIndex
= rLine
.GetStart();
1068 tools::Long nTmpX
= rLine
.GetStartX();
1069 if ( nXPos
<= nTmpX
)
1072 for ( std::size_t i
= rLine
.GetStartPortion(); i
<= rLine
.GetEndPortion(); i
++ )
1074 TETextPortion
& rTextPortion
= pPortion
->GetTextPortions()[ i
];
1075 nTmpX
+= rTextPortion
.GetWidth();
1077 if ( nTmpX
> nXPos
)
1079 if( rTextPortion
.GetLen() > 1 )
1081 nTmpX
-= rTextPortion
.GetWidth(); // position before Portion
1082 // TODO: Optimize: no GetTextBreak if fixed-width Font
1084 SeekCursor( nPortion
, nCurIndex
+1, aFont
, nullptr );
1085 mpRefDev
->SetFont( aFont
);
1086 tools::Long nPosInPortion
= nXPos
-nTmpX
;
1087 if ( IsRightToLeft() != rTextPortion
.IsRightToLeft() )
1088 nPosInPortion
= rTextPortion
.GetWidth() - nPosInPortion
;
1089 nCurIndex
= mpRefDev
->GetTextBreak( pPortion
->GetNode()->GetText(), nPosInPortion
, nCurIndex
);
1090 // MT: GetTextBreak should assure that we are not within a CTL cell...
1094 nCurIndex
+= rTextPortion
.GetLen();
1099 tools::Long
TextEngine::GetTextHeight() const
1101 SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
1103 if ( !IsFormatted() && !IsFormatting() )
1104 const_cast<TextEngine
*>(this)->FormatAndUpdate();
1106 return mnCurTextHeight
;
1109 tools::Long
TextEngine::GetTextHeight( sal_uInt32 nParagraph
) const
1111 SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
1113 if ( !IsFormatted() && !IsFormatting() )
1114 const_cast<TextEngine
*>(this)->FormatAndUpdate();
1116 return CalcParaHeight( nParagraph
);
1119 tools::Long
TextEngine::CalcTextWidth( sal_uInt32 nPara
)
1121 tools::Long nParaWidth
= 0;
1122 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( nPara
);
1123 for ( auto nLine
= pPortion
->GetLines().size(); nLine
; )
1125 tools::Long nLineWidth
= 0;
1126 TextLine
& rLine
= pPortion
->GetLines()[ --nLine
];
1127 for ( std::size_t nTP
= rLine
.GetStartPortion(); nTP
<= rLine
.GetEndPortion(); nTP
++ )
1129 TETextPortion
& rTextPortion
= pPortion
->GetTextPortions()[ nTP
];
1130 nLineWidth
+= rTextPortion
.GetWidth();
1132 if ( nLineWidth
> nParaWidth
)
1133 nParaWidth
= nLineWidth
;
1138 tools::Long
TextEngine::CalcTextWidth()
1140 if ( !IsFormatted() && !IsFormatting() )
1143 if ( mnCurTextWidth
< 0 )
1146 for ( sal_uInt32 nPara
= mpTEParaPortions
->Count(); nPara
; )
1148 const tools::Long nParaWidth
= CalcTextWidth( --nPara
);
1149 if ( nParaWidth
> mnCurTextWidth
)
1150 mnCurTextWidth
= nParaWidth
;
1153 return mnCurTextWidth
+1;// wider by 1, as CreateLines breaks at >=
1156 tools::Long
TextEngine::CalcTextHeight() const
1158 SAL_WARN_IF( !GetUpdateMode(), "vcl", "CalcTextHeight: GetUpdateMode()" );
1161 for ( auto nPortion
= mpTEParaPortions
->Count(); nPortion
; )
1162 nY
+= CalcParaHeight( --nPortion
);
1166 tools::Long
TextEngine::CalcTextWidth( sal_uInt32 nPara
, sal_Int32 nPortionStart
, sal_Int32 nLen
)
1169 // within the text there must not be a Portion change (attribute/tab)!
1170 sal_Int32 nTabPos
= mpDoc
->GetNodes()[ nPara
]->GetText().indexOf( '\t', nPortionStart
);
1171 SAL_WARN_IF( nTabPos
!= -1 && nTabPos
< (nPortionStart
+nLen
), "vcl", "CalcTextWidth: Tab!" );
1175 SeekCursor( nPara
, nPortionStart
+1, aFont
, nullptr );
1176 mpRefDev
->SetFont( aFont
);
1177 TextNode
* pNode
= mpDoc
->GetNodes()[ nPara
].get();
1178 tools::Long nWidth
= mpRefDev
->GetTextWidth( pNode
->GetText(), nPortionStart
, nLen
);
1182 void TextEngine::GetTextPortionRange(const TextPaM
& rPaM
, sal_Int32
& nStart
, sal_Int32
& nEnd
)
1186 TEParaPortion
* pParaPortion
= mpTEParaPortions
->GetObject( rPaM
.GetPara() );
1187 for ( std::size_t i
= 0; i
< pParaPortion
->GetTextPortions().size(); ++i
)
1189 TETextPortion
& rTextPortion
= pParaPortion
->GetTextPortions()[ i
];
1190 if (nStart
+ rTextPortion
.GetLen() > rPaM
.GetIndex())
1192 nEnd
= nStart
+ rTextPortion
.GetLen();
1197 nStart
+= rTextPortion
.GetLen();
1202 sal_uInt16
TextEngine::GetLineCount( sal_uInt32 nParagraph
) const
1204 SAL_WARN_IF( nParagraph
>= mpTEParaPortions
->Count(), "vcl", "GetLineCount: Out of range" );
1206 TEParaPortion
* pPPortion
= mpTEParaPortions
->GetObject( nParagraph
);
1208 return pPPortion
->GetLines().size();
1213 sal_Int32
TextEngine::GetLineLen( sal_uInt32 nParagraph
, sal_uInt16 nLine
) const
1215 SAL_WARN_IF( nParagraph
>= mpTEParaPortions
->Count(), "vcl", "GetLineCount: Out of range" );
1217 TEParaPortion
* pPPortion
= mpTEParaPortions
->GetObject( nParagraph
);
1218 if ( pPPortion
&& ( nLine
< pPPortion
->GetLines().size() ) )
1220 return pPPortion
->GetLines()[ nLine
].GetLen();
1226 tools::Long
TextEngine::CalcParaHeight( sal_uInt32 nParagraph
) const
1228 tools::Long nHeight
= 0;
1230 TEParaPortion
* pPPortion
= mpTEParaPortions
->GetObject( nParagraph
);
1231 SAL_WARN_IF( !pPPortion
, "vcl", "GetParaHeight: paragraph not found" );
1233 nHeight
= pPPortion
->GetLines().size() * mnCharHeight
;
1238 Range
TextEngine::GetInvalidYOffsets( sal_uInt32 nPortion
)
1240 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nPortion
);
1241 sal_uInt16 nLines
= pTEParaPortion
->GetLines().size();
1242 sal_uInt16 nLastInvalid
, nFirstInvalid
= 0;
1244 for ( nLine
= 0; nLine
< nLines
; nLine
++ )
1246 TextLine
& rL
= pTEParaPortion
->GetLines()[ nLine
];
1247 if ( rL
.IsInvalid() )
1249 nFirstInvalid
= nLine
;
1254 for ( nLastInvalid
= nFirstInvalid
; nLastInvalid
< nLines
; nLastInvalid
++ )
1256 TextLine
& rL
= pTEParaPortion
->GetLines()[ nLine
];
1261 if ( nLastInvalid
>= nLines
)
1262 nLastInvalid
= nLines
-1;
1264 return Range( nFirstInvalid
*mnCharHeight
, ((nLastInvalid
+1)*mnCharHeight
)-1 );
1267 sal_uInt32
TextEngine::GetParagraphCount() const
1269 return static_cast<sal_uInt32
>(mpDoc
->GetNodes().size());
1272 void TextEngine::EnableUndo( bool bEnable
)
1274 // delete list when switching mode
1275 if ( bEnable
!= IsUndoEnabled() )
1278 mbUndoEnabled
= bEnable
;
1281 SfxUndoManager
& TextEngine::GetUndoManager()
1283 if ( !mpUndoManager
)
1284 mpUndoManager
.reset( new TextUndoManager( this ) );
1285 return *mpUndoManager
;
1288 void TextEngine::UndoActionStart( sal_uInt16 nId
)
1290 if ( IsUndoEnabled() && !IsInUndo() )
1292 GetUndoManager().EnterListAction( OUString(), OUString(), nId
, ViewShellId(-1) );
1296 void TextEngine::UndoActionEnd()
1298 if ( IsUndoEnabled() && !IsInUndo() )
1299 GetUndoManager().LeaveListAction();
1302 void TextEngine::InsertUndo( std::unique_ptr
<TextUndo
> pUndo
, bool bTryMerge
)
1304 SAL_WARN_IF( IsInUndo(), "vcl", "InsertUndo: in Undo mode!" );
1305 GetUndoManager().AddUndoAction( std::move(pUndo
), bTryMerge
);
1308 void TextEngine::ResetUndo()
1310 if ( mpUndoManager
)
1311 mpUndoManager
->Clear();
1314 void TextEngine::InsertContent( std::unique_ptr
<TextNode
> pNode
, sal_uInt32 nPara
)
1316 SAL_WARN_IF( !pNode
, "vcl", "InsertContent: NULL-Pointer!" );
1317 SAL_WARN_IF( !IsInUndo(), "vcl", "InsertContent: only in Undo()!" );
1318 TEParaPortion
* pNew
= new TEParaPortion( pNode
.get() );
1319 mpTEParaPortions
->Insert( pNew
, nPara
);
1320 mpDoc
->GetNodes().insert( mpDoc
->GetNodes().begin() + nPara
, std::move(pNode
) );
1321 ImpParagraphInserted( nPara
);
1324 TextPaM
TextEngine::SplitContent( sal_uInt32 nNode
, sal_Int32 nSepPos
)
1327 TextNode
* pNode
= mpDoc
->GetNodes()[ nNode
].get();
1328 SAL_WARN_IF( !pNode
, "vcl", "SplitContent: Invalid Node!" );
1329 SAL_WARN_IF( !IsInUndo(), "vcl", "SplitContent: only in Undo()!" );
1330 SAL_WARN_IF( nSepPos
> pNode
->GetText().getLength(), "vcl", "SplitContent: Bad index" );
1332 TextPaM
aPaM( nNode
, nSepPos
);
1333 return ImpInsertParaBreak( aPaM
);
1336 TextPaM
TextEngine::ConnectContents( sal_uInt32 nLeftNode
)
1338 SAL_WARN_IF( !IsInUndo(), "vcl", "ConnectContent: only in Undo()!" );
1339 return ImpConnectParagraphs( nLeftNode
, nLeftNode
+1 );
1342 void TextEngine::SeekCursor( sal_uInt32 nPara
, sal_Int32 nPos
, vcl::Font
& rFont
, OutputDevice
* pOutDev
)
1346 pOutDev
->SetTextColor( maTextColor
);
1348 TextNode
* pNode
= mpDoc
->GetNodes()[ nPara
].get();
1349 sal_uInt16 nAttribs
= pNode
->GetCharAttribs().Count();
1350 for ( sal_uInt16 nAttr
= 0; nAttr
< nAttribs
; nAttr
++ )
1352 TextCharAttrib
& rAttrib
= pNode
->GetCharAttribs().GetAttrib( nAttr
);
1353 if ( rAttrib
.GetStart() > nPos
)
1356 // When seeking don't use Attr that start there!
1357 // Do not use empty attributes:
1358 // - If just being setup and empty => no effect on Font
1359 // - Characters that are setup in an empty paragraph become visible right away.
1360 if ( ( ( rAttrib
.GetStart() < nPos
) && ( rAttrib
.GetEnd() >= nPos
) )
1361 || pNode
->GetText().isEmpty() )
1363 if ( rAttrib
.Which() != TEXTATTR_FONTCOLOR
)
1365 rAttrib
.GetAttr().SetFont(rFont
);
1370 pOutDev
->SetTextColor( static_cast<const TextAttribFontColor
&>(rAttrib
.GetAttr()).GetColor() );
1375 if ( !(mpIMEInfos
&& mpIMEInfos
->pAttribs
&& ( mpIMEInfos
->aPos
.GetPara() == nPara
) &&
1376 ( nPos
> mpIMEInfos
->aPos
.GetIndex() ) && ( nPos
<= ( mpIMEInfos
->aPos
.GetIndex() + mpIMEInfos
->nLen
) )) )
1379 ExtTextInputAttr nAttr
= mpIMEInfos
->pAttribs
[ nPos
- mpIMEInfos
->aPos
.GetIndex() - 1 ];
1380 if ( nAttr
& ExtTextInputAttr::Underline
)
1381 rFont
.SetUnderline( LINESTYLE_SINGLE
);
1382 else if ( nAttr
& ExtTextInputAttr::DoubleUnderline
)
1383 rFont
.SetUnderline( LINESTYLE_DOUBLE
);
1384 else if ( nAttr
& ExtTextInputAttr::BoldUnderline
)
1385 rFont
.SetUnderline( LINESTYLE_BOLD
);
1386 else if ( nAttr
& ExtTextInputAttr::DottedUnderline
)
1387 rFont
.SetUnderline( LINESTYLE_DOTTED
);
1388 else if ( nAttr
& ExtTextInputAttr::DashDotUnderline
)
1389 rFont
.SetUnderline( LINESTYLE_DOTTED
);
1390 if ( nAttr
& ExtTextInputAttr::RedText
)
1391 rFont
.SetColor( COL_RED
);
1392 else if ( nAttr
& ExtTextInputAttr::HalfToneText
)
1393 rFont
.SetColor( COL_LIGHTGRAY
);
1394 if ( nAttr
& ExtTextInputAttr::Highlight
)
1396 const StyleSettings
& rStyleSettings
= Application::GetSettings().GetStyleSettings();
1397 rFont
.SetColor( rStyleSettings
.GetHighlightTextColor() );
1398 rFont
.SetFillColor( rStyleSettings
.GetHighlightColor() );
1399 rFont
.SetTransparent( false );
1401 else if ( nAttr
& ExtTextInputAttr::GrayWaveline
)
1403 rFont
.SetUnderline( LINESTYLE_WAVE
);
1405 // pOut->SetTextLineColor( COL_LIGHTGRAY );
1409 void TextEngine::FormatAndUpdate( TextView
* pCurView
)
1415 IdleFormatAndUpdate( pCurView
);
1419 UpdateViews( pCurView
);
1423 void TextEngine::IdleFormatAndUpdate( TextView
* pCurView
, sal_uInt16 nMaxTimerRestarts
)
1425 mpIdleFormatter
->DoIdleFormat( pCurView
, nMaxTimerRestarts
);
1428 void TextEngine::TextModified()
1430 mbFormatted
= false;
1434 void TextEngine::UpdateViews( TextView
* pCurView
)
1436 if ( !GetUpdateMode() || IsFormatting() || maInvalidRect
.IsEmpty() )
1439 SAL_WARN_IF( !IsFormatted(), "vcl", "UpdateViews: Doc not formatted!" );
1441 for (TextView
* pView
: *mpViews
)
1443 pView
->HideCursor();
1445 tools::Rectangle
aClipRect( maInvalidRect
);
1446 const Size aOutSz
= pView
->GetWindow()->GetOutputSizePixel();
1447 const tools::Rectangle
aVisArea( pView
->GetStartDocPos(), aOutSz
);
1448 aClipRect
.Intersection( aVisArea
);
1449 if ( !aClipRect
.IsEmpty() )
1451 // translate into window coordinates
1452 Point aNewPos
= pView
->GetWindowPos( aClipRect
.TopLeft() );
1453 if ( IsRightToLeft() )
1454 aNewPos
.AdjustX( -(aOutSz
.Width() - 1) );
1455 aClipRect
.SetPos( aNewPos
);
1457 pView
->GetWindow()->Invalidate( aClipRect
);
1463 pCurView
->ShowCursor( pCurView
->IsAutoScroll() );
1466 maInvalidRect
= tools::Rectangle();
1469 IMPL_LINK_NOARG(TextEngine
, IdleFormatHdl
, Timer
*, void)
1471 FormatAndUpdate( mpIdleFormatter
->GetView() );
1474 void TextEngine::CheckIdleFormatter()
1476 mpIdleFormatter
->ForceTimeout();
1479 void TextEngine::FormatFullDoc()
1481 for ( sal_uInt32 nPortion
= 0; nPortion
< mpTEParaPortions
->Count(); ++nPortion
)
1483 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nPortion
);
1484 pTEParaPortion
->MarkSelectionInvalid( 0 );
1486 mbFormatted
= false;
1490 void TextEngine::FormatDoc()
1492 if ( IsFormatted() || !GetUpdateMode() || IsFormatting() )
1495 mbIsFormatting
= true;
1496 mbHasMultiLineParas
= false;
1501 maInvalidRect
= tools::Rectangle(); // clear
1502 for ( sal_uInt32 nPara
= 0; nPara
< mpTEParaPortions
->Count(); ++nPara
)
1504 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nPara
);
1505 if ( pTEParaPortion
->IsInvalid() )
1507 const tools::Long nOldParaWidth
= mnCurTextWidth
>= 0 ? CalcTextWidth( nPara
) : -1;
1509 Broadcast( TextHint( SfxHintId::TextFormatPara
, nPara
) );
1511 if ( CreateLines( nPara
) )
1514 // set InvalidRect only once
1515 if ( maInvalidRect
.IsEmpty() )
1517 // otherwise remains Empty() for Paperwidth 0 (AutoPageSize)
1518 const tools::Long nWidth
= mnMaxTextWidth
1520 : std::numeric_limits
<tools::Long
>::max();
1521 const Range
aInvRange( GetInvalidYOffsets( nPara
) );
1522 maInvalidRect
= tools::Rectangle( Point( 0, nY
+aInvRange
.Min() ),
1523 Size( nWidth
, aInvRange
.Len() ) );
1527 maInvalidRect
.SetBottom( nY
+ CalcParaHeight( nPara
) );
1530 if ( mnCurTextWidth
>= 0 )
1532 const tools::Long nNewParaWidth
= CalcTextWidth( nPara
);
1533 if ( nNewParaWidth
>= mnCurTextWidth
)
1534 mnCurTextWidth
= nNewParaWidth
;
1535 else if ( nOldParaWidth
>= mnCurTextWidth
)
1536 mnCurTextWidth
= -1;
1541 maInvalidRect
.SetBottom( nY
+ CalcParaHeight( nPara
) );
1543 nY
+= CalcParaHeight( nPara
);
1544 if ( !mbHasMultiLineParas
&& pTEParaPortion
->GetLines().size() > 1 )
1545 mbHasMultiLineParas
= true;
1548 if ( !maInvalidRect
.IsEmpty() )
1550 const tools::Long nNewHeight
= CalcTextHeight();
1551 const tools::Long nDiff
= nNewHeight
- mnCurTextHeight
;
1552 if ( nNewHeight
< mnCurTextHeight
)
1554 maInvalidRect
.SetBottom( std::max( nNewHeight
, mnCurTextHeight
) );
1555 if ( maInvalidRect
.IsEmpty() )
1557 maInvalidRect
.SetTop( 0 );
1558 // Left and Right are not evaluated, but set because of IsEmpty
1559 maInvalidRect
.SetLeft( 0 );
1560 maInvalidRect
.SetRight( mnMaxTextWidth
);
1564 mnCurTextHeight
= nNewHeight
;
1568 Broadcast( TextHint( SfxHintId::TextHeightChanged
) );
1572 mbIsFormatting
= false;
1575 Broadcast( TextHint( SfxHintId::TextFormatted
) );
1578 void TextEngine::CreateAndInsertEmptyLine( sal_uInt32 nPara
)
1580 TextNode
* pNode
= mpDoc
->GetNodes()[ nPara
].get();
1581 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nPara
);
1584 aTmpLine
.SetStart( pNode
->GetText().getLength() );
1585 aTmpLine
.SetEnd( aTmpLine
.GetStart() );
1587 if ( ImpGetAlign() == TxtAlign::Center
)
1588 aTmpLine
.SetStartX( static_cast<short>(mnMaxTextWidth
/ 2) );
1589 else if ( ImpGetAlign() == TxtAlign::Right
)
1590 aTmpLine
.SetStartX( static_cast<short>(mnMaxTextWidth
) );
1592 aTmpLine
.SetStartX( mpDoc
->GetLeftMargin() );
1594 bool bLineBreak
= !pNode
->GetText().isEmpty();
1596 TETextPortion
aDummyPortion( 0 );
1597 aDummyPortion
.GetWidth() = 0;
1598 pTEParaPortion
->GetTextPortions().push_back( aDummyPortion
);
1602 // -2: The new one is already inserted.
1603 const std::size_t nPos
= pTEParaPortion
->GetTextPortions().size() - 1;
1604 aTmpLine
.SetStartPortion( nPos
);
1605 aTmpLine
.SetEndPortion( nPos
);
1607 pTEParaPortion
->GetLines().push_back( aTmpLine
);
1610 void TextEngine::ImpBreakLine( sal_uInt32 nPara
, TextLine
* pLine
, sal_Int32 nPortionStart
, tools::Long nRemainingWidth
)
1612 TextNode
* pNode
= mpDoc
->GetNodes()[ nPara
].get();
1614 // Font still should be adjusted
1615 sal_Int32 nMaxBreakPos
= mpRefDev
->GetTextBreak( pNode
->GetText(), nRemainingWidth
, nPortionStart
);
1617 SAL_WARN_IF( nMaxBreakPos
>= pNode
->GetText().getLength(), "vcl", "ImpBreakLine: Break?!" );
1619 if ( nMaxBreakPos
== -1 ) // GetTextBreak() != GetTextSize()
1620 nMaxBreakPos
= pNode
->GetText().getLength() - 1;
1622 uno::Reference
< i18n::XBreakIterator
> xBI
= GetBreakIterator();
1623 i18n::LineBreakHyphenationOptions
aHyphOptions( nullptr, uno::Sequence
< beans::PropertyValue
>(), 1 );
1625 i18n::LineBreakUserOptions aUserOptions
;
1626 aUserOptions
.forbiddenBeginCharacters
= ImpGetLocaleDataWrapper()->getForbiddenCharacters().beginLine
;
1627 aUserOptions
.forbiddenEndCharacters
= ImpGetLocaleDataWrapper()->getForbiddenCharacters().endLine
;
1628 aUserOptions
.applyForbiddenRules
= true;
1629 aUserOptions
.allowPunctuationOutsideMargin
= false;
1630 aUserOptions
.allowHyphenateEnglish
= false;
1632 static const css::lang::Locale aDefLocale
;
1633 i18n::LineBreakResults aLBR
= xBI
->getLineBreak( pNode
->GetText(), nMaxBreakPos
, aDefLocale
, pLine
->GetStart(), aHyphOptions
, aUserOptions
);
1634 sal_Int32 nBreakPos
= aLBR
.breakIndex
;
1635 if ( nBreakPos
<= pLine
->GetStart() )
1637 nBreakPos
= nMaxBreakPos
;
1638 if ( nBreakPos
<= pLine
->GetStart() )
1639 nBreakPos
= pLine
->GetStart() + 1; // infinite loop otherwise!
1642 // the damaged Portion is the End Portion
1643 pLine
->SetEnd( nBreakPos
);
1644 const std::size_t nEndPortion
= SplitTextPortion( nPara
, nBreakPos
);
1646 if ( nBreakPos
>= pLine
->GetStart() &&
1647 nBreakPos
< pNode
->GetText().getLength() &&
1648 pNode
->GetText()[ nBreakPos
] == ' ' )
1650 // generally suppress blanks at the end of line
1651 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nPara
);
1652 TETextPortion
& rTP
= pTEParaPortion
->GetTextPortions()[ nEndPortion
];
1653 SAL_WARN_IF( nBreakPos
<= pLine
->GetStart(), "vcl", "ImpBreakLine: SplitTextPortion at beginning of line?" );
1654 rTP
.GetWidth() = CalcTextWidth( nPara
, nBreakPos
-rTP
.GetLen(), rTP
.GetLen()-1 );
1656 pLine
->SetEndPortion( nEndPortion
);
1659 std::size_t TextEngine::SplitTextPortion( sal_uInt32 nPara
, sal_Int32 nPos
)
1662 // the Portion at nPos is being split, unless there is already a switch at nPos
1666 std::size_t nSplitPortion
;
1667 sal_Int32 nTmpPos
= 0;
1668 TETextPortion
* pTextPortion
= nullptr;
1669 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nPara
);
1670 const std::size_t nPortions
= pTEParaPortion
->GetTextPortions().size();
1671 for ( nSplitPortion
= 0; nSplitPortion
< nPortions
; nSplitPortion
++ )
1673 TETextPortion
& rTP
= pTEParaPortion
->GetTextPortions()[nSplitPortion
];
1674 nTmpPos
+= rTP
.GetLen();
1675 if ( nTmpPos
>= nPos
)
1677 if ( nTmpPos
== nPos
) // nothing needs splitting
1678 return nSplitPortion
;
1679 pTextPortion
= &rTP
;
1684 assert(pTextPortion
&& "SplitTextPortion: position outside of region!");
1686 const sal_Int32 nOverlapp
= nTmpPos
- nPos
;
1687 pTextPortion
->GetLen() -= nOverlapp
;
1688 pTextPortion
->GetWidth() = CalcTextWidth( nPara
, nPos
-pTextPortion
->GetLen(), pTextPortion
->GetLen() );
1689 TETextPortion
aNewPortion( nOverlapp
);
1690 pTEParaPortion
->GetTextPortions().insert( pTEParaPortion
->GetTextPortions().begin() + nSplitPortion
+ 1, aNewPortion
);
1692 return nSplitPortion
;
1695 void TextEngine::CreateTextPortions( sal_uInt32 nPara
, sal_Int32 nStartPos
)
1697 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nPara
);
1698 TextNode
* pNode
= pTEParaPortion
->GetNode();
1699 SAL_WARN_IF( pNode
->GetText().isEmpty(), "vcl", "CreateTextPortions: should not be used for empty paragraphs!" );
1701 o3tl::sorted_vector
<sal_Int32
> aPositions
;
1702 o3tl::sorted_vector
<sal_Int32
>::const_iterator aPositionsIt
;
1703 aPositions
.insert(0);
1705 const sal_uInt16 nAttribs
= pNode
->GetCharAttribs().Count();
1706 for ( sal_uInt16 nAttr
= 0; nAttr
< nAttribs
; nAttr
++ )
1708 TextCharAttrib
& rAttrib
= pNode
->GetCharAttribs().GetAttrib( nAttr
);
1710 aPositions
.insert( rAttrib
.GetStart() );
1711 aPositions
.insert( rAttrib
.GetEnd() );
1713 aPositions
.insert( pNode
->GetText().getLength() );
1715 const std::vector
<TEWritingDirectionInfo
>& rWritingDirections
= pTEParaPortion
->GetWritingDirectionInfos();
1716 for ( const auto& rWritingDirection
: rWritingDirections
)
1717 aPositions
.insert( rWritingDirection
.nStartPos
);
1719 if ( mpIMEInfos
&& mpIMEInfos
->pAttribs
&& ( mpIMEInfos
->aPos
.GetPara() == nPara
) )
1721 ExtTextInputAttr nLastAttr
= ExtTextInputAttr(0xffff);
1722 for( sal_Int32 n
= 0; n
< mpIMEInfos
->nLen
; n
++ )
1724 if ( mpIMEInfos
->pAttribs
[n
] != nLastAttr
)
1726 aPositions
.insert( mpIMEInfos
->aPos
.GetIndex() + n
);
1727 nLastAttr
= mpIMEInfos
->pAttribs
[n
];
1732 sal_Int32 nTabPos
= pNode
->GetText().indexOf( '\t' );
1733 while ( nTabPos
!= -1 )
1735 aPositions
.insert( nTabPos
);
1736 aPositions
.insert( nTabPos
+ 1 );
1737 nTabPos
= pNode
->GetText().indexOf( '\t', nTabPos
+1 );
1740 // Delete starting with...
1741 // Unfortunately, the number of TextPortions does not have to be
1742 // equal to aPositions.Count(), because of linebreaks
1743 sal_Int32 nPortionStart
= 0;
1744 std::size_t nInvPortion
= 0;
1746 for ( nP
= 0; nP
< pTEParaPortion
->GetTextPortions().size(); nP
++ )
1748 TETextPortion
& rTmpPortion
= pTEParaPortion
->GetTextPortions()[nP
];
1749 nPortionStart
+= rTmpPortion
.GetLen();
1750 if ( nPortionStart
>= nStartPos
)
1752 nPortionStart
-= rTmpPortion
.GetLen();
1757 OSL_ENSURE(nP
< pTEParaPortion
->GetTextPortions().size()
1758 || pTEParaPortion
->GetTextPortions().empty(),
1759 "CreateTextPortions: Nothing to delete!");
1760 if ( nInvPortion
&& ( nPortionStart
+pTEParaPortion
->GetTextPortions()[nInvPortion
].GetLen() > nStartPos
) )
1762 // better one before...
1763 // But only if it was within the Portion; otherwise it might be
1764 // the only one in the previous line!
1766 nPortionStart
-= pTEParaPortion
->GetTextPortions()[nInvPortion
].GetLen();
1768 pTEParaPortion
->GetTextPortions().DeleteFromPortion( nInvPortion
);
1770 // a Portion might have been created by a line break
1771 aPositions
.insert( nPortionStart
);
1773 aPositionsIt
= aPositions
.find( nPortionStart
);
1774 SAL_WARN_IF( aPositionsIt
== aPositions
.end(), "vcl", "CreateTextPortions: nPortionStart not found" );
1776 if ( aPositionsIt
!= aPositions
.end() )
1778 o3tl::sorted_vector
<sal_Int32
>::const_iterator nextIt
= aPositionsIt
;
1779 for ( ++nextIt
; nextIt
!= aPositions
.end(); ++aPositionsIt
, ++nextIt
)
1781 TETextPortion
aNew( *nextIt
- *aPositionsIt
);
1782 pTEParaPortion
->GetTextPortions().push_back( aNew
);
1785 OSL_ENSURE(pTEParaPortion
->GetTextPortions().size(), "CreateTextPortions: No Portions?!");
1788 void TextEngine::RecalcTextPortion( sal_uInt32 nPara
, sal_Int32 nStartPos
, sal_Int32 nNewChars
)
1790 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nPara
);
1791 OSL_ENSURE(pTEParaPortion
->GetTextPortions().size(), "RecalcTextPortion: no Portions!");
1792 OSL_ENSURE(nNewChars
, "RecalcTextPortion: Diff == 0");
1794 TextNode
* const pNode
= pTEParaPortion
->GetNode();
1795 if ( nNewChars
> 0 )
1797 // If an Attribute is starting/ending at nStartPos, or there is a tab
1798 // before nStartPos => a new Portion starts.
1799 // Otherwise the Portion is extended at nStartPos.
1800 // Or if at the very beginning ( StartPos 0 ) followed by a tab...
1801 if ( ( pNode
->GetCharAttribs().HasBoundingAttrib( nStartPos
) ) ||
1802 ( nStartPos
&& ( pNode
->GetText()[ nStartPos
- 1 ] == '\t' ) ) ||
1803 ( !nStartPos
&& ( nNewChars
< pNode
->GetText().getLength() ) && pNode
->GetText()[ nNewChars
] == '\t' ) )
1805 std::size_t nNewPortionPos
= 0;
1807 nNewPortionPos
= SplitTextPortion( nPara
, nStartPos
) + 1;
1809 // Here could be an empty Portion if the paragraph was empty,
1810 // or a new line was created by a hard line-break.
1811 if ( ( nNewPortionPos
< pTEParaPortion
->GetTextPortions().size() ) &&
1812 !pTEParaPortion
->GetTextPortions()[nNewPortionPos
].GetLen() )
1814 // use the empty Portion
1815 pTEParaPortion
->GetTextPortions()[nNewPortionPos
].GetLen() = nNewChars
;
1819 TETextPortion
aNewPortion( nNewChars
);
1820 pTEParaPortion
->GetTextPortions().insert( pTEParaPortion
->GetTextPortions().begin() + nNewPortionPos
, aNewPortion
);
1825 sal_Int32 nPortionStart
{0};
1826 const std::size_t nTP
= pTEParaPortion
->GetTextPortions().FindPortion( nStartPos
, nPortionStart
);
1827 TETextPortion
& rTP
= pTEParaPortion
->GetTextPortions()[ nTP
];
1828 rTP
.GetLen() += nNewChars
;
1829 rTP
.GetWidth() = -1;
1834 // Shrink or remove Portion
1835 // Before calling this function, ensure that no Portions were in the deleted range!
1837 // There must be no Portion reaching into or starting within,
1838 // thus: nStartPos <= nPos <= nStartPos - nNewChars(neg.)
1839 std::size_t nPortion
= 0;
1841 const sal_Int32 nEnd
= nStartPos
-nNewChars
;
1842 const std::size_t nPortions
= pTEParaPortion
->GetTextPortions().size();
1843 TETextPortion
* pTP
= nullptr;
1844 for ( nPortion
= 0; nPortion
< nPortions
; nPortion
++ )
1846 pTP
= &pTEParaPortion
->GetTextPortions()[ nPortion
];
1847 if ( ( nPos
+pTP
->GetLen() ) > nStartPos
)
1849 SAL_WARN_IF( nPos
> nStartPos
, "vcl", "RecalcTextPortion: Bad Start!" );
1850 SAL_WARN_IF( nPos
+pTP
->GetLen() < nEnd
, "vcl", "RecalcTextPortion: Bad End!" );
1853 nPos
+= pTP
->GetLen();
1855 SAL_WARN_IF( !pTP
, "vcl", "RecalcTextPortion: Portion not found!" );
1856 if ( ( nPos
== nStartPos
) && ( (nPos
+pTP
->GetLen()) == nEnd
) )
1859 pTEParaPortion
->GetTextPortions().erase( pTEParaPortion
->GetTextPortions().begin() + nPortion
);
1863 SAL_WARN_IF( pTP
->GetLen() <= (-nNewChars
), "vcl", "RecalcTextPortion: Portion too small to shrink!" );
1864 pTP
->GetLen() += nNewChars
;
1866 OSL_ENSURE( pTEParaPortion
->GetTextPortions().size(),
1867 "RecalcTextPortion: none are left!" );
1871 void TextEngine::ImpPaint( OutputDevice
* pOutDev
, const Point
& rStartPos
, tools::Rectangle
const* pPaintArea
, TextSelection
const* pSelection
)
1873 if ( !GetUpdateMode() )
1876 if ( !IsFormatted() )
1879 vcl::Window
* const pOutWin
= pOutDev
->GetOwnerWindow();
1880 const bool bTransparent
= (pOutWin
&& pOutWin
->IsPaintTransparent());
1882 tools::Long nY
= rStartPos
.Y();
1884 TextPaM
const* pSelStart
= nullptr;
1885 TextPaM
const* pSelEnd
= nullptr;
1886 if ( pSelection
&& pSelection
->HasRange() )
1888 const bool bInvers
= pSelection
->GetEnd() < pSelection
->GetStart();
1889 pSelStart
= !bInvers
? &pSelection
->GetStart() : &pSelection
->GetEnd();
1890 pSelEnd
= bInvers
? &pSelection
->GetStart() : &pSelection
->GetEnd();
1893 const StyleSettings
& rStyleSettings
= pOutDev
->GetSettings().GetStyleSettings();
1895 // for all paragraphs
1896 for ( sal_uInt32 nPara
= 0; nPara
< mpTEParaPortions
->Count(); ++nPara
)
1898 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( nPara
);
1899 // in case while typing Idle-Formatting, asynchronous Paint
1900 if ( pPortion
->IsInvalid() )
1903 const tools::Long nParaHeight
= CalcParaHeight( nPara
);
1904 if ( !pPaintArea
|| ( ( nY
+ nParaHeight
) > pPaintArea
->Top() ) )
1906 // for all lines of the paragraph
1907 sal_Int32 nIndex
= 0;
1908 for ( auto & rLine
: pPortion
->GetLines() )
1910 Point
aTmpPos( rStartPos
.X() + rLine
.GetStartX(), nY
);
1912 if ( !pPaintArea
|| ( ( nY
+ mnCharHeight
) > pPaintArea
->Top() ) )
1914 // for all Portions of the line
1915 nIndex
= rLine
.GetStart();
1916 for ( std::size_t y
= rLine
.GetStartPortion(); y
<= rLine
.GetEndPortion(); y
++ )
1918 OSL_ENSURE(pPortion
->GetTextPortions().size(),
1919 "ImpPaint: Line without Textportion!");
1920 TETextPortion
& rTextPortion
= pPortion
->GetTextPortions()[ y
];
1922 ImpInitLayoutMode( pOutDev
/*, pTextPortion->IsRightToLeft() */);
1924 const tools::Long nTxtWidth
= rTextPortion
.GetWidth();
1925 aTmpPos
.setX( rStartPos
.X() + ImpGetOutputOffset( nPara
, &rLine
, nIndex
, nIndex
) );
1927 // only print if starting in the visible region
1928 if ( ( aTmpPos
.X() + nTxtWidth
) >= 0 )
1930 switch ( rTextPortion
.GetKind() )
1932 case PORTIONKIND_TEXT
:
1935 SeekCursor( nPara
, nIndex
+1, aFont
, pOutDev
);
1937 aFont
.SetTransparent( true );
1938 else if ( pSelection
)
1939 aFont
.SetTransparent( false );
1940 pOutDev
->SetFont( aFont
);
1942 sal_Int32 nTmpIndex
= nIndex
;
1943 sal_Int32 nEnd
= nTmpIndex
+ rTextPortion
.GetLen();
1944 Point aPos
= aTmpPos
;
1949 // is a part of it in the selection?
1950 const TextPaM
aTextStart( nPara
, nTmpIndex
);
1951 const TextPaM
aTextEnd( nPara
, nEnd
);
1952 if ( ( aTextStart
< *pSelEnd
) && ( aTextEnd
> *pSelStart
) )
1954 // 1) vcl::Region before Selection
1955 if ( aTextStart
< *pSelStart
)
1957 const sal_Int32 nL
= pSelStart
->GetIndex() - nTmpIndex
;
1958 pOutDev
->SetFont( aFont
);
1959 pOutDev
->SetTextFillColor();
1960 aPos
.setX( rStartPos
.X() + ImpGetOutputOffset( nPara
, &rLine
, nTmpIndex
, nTmpIndex
+nL
) );
1961 pOutDev
->DrawText( aPos
, pPortion
->GetNode()->GetText(), nTmpIndex
, nL
);
1962 nTmpIndex
= nTmpIndex
+ nL
;
1965 // 2) vcl::Region with Selection
1966 sal_Int32 nL
= nEnd
- nTmpIndex
;
1967 if ( aTextEnd
> *pSelEnd
)
1968 nL
= pSelEnd
->GetIndex() - nTmpIndex
;
1971 const Color aOldTextColor
= pOutDev
->GetTextColor();
1972 pOutDev
->SetTextColor( rStyleSettings
.GetHighlightTextColor() );
1973 pOutDev
->SetTextFillColor( rStyleSettings
.GetHighlightColor() );
1974 aPos
.setX( rStartPos
.X() + ImpGetOutputOffset( nPara
, &rLine
, nTmpIndex
, nTmpIndex
+nL
) );
1975 pOutDev
->DrawText( aPos
, pPortion
->GetNode()->GetText(), nTmpIndex
, nL
);
1976 pOutDev
->SetTextColor( aOldTextColor
);
1977 pOutDev
->SetTextFillColor();
1978 nTmpIndex
= nTmpIndex
+ nL
;
1981 // 3) vcl::Region after Selection
1982 if ( nTmpIndex
< nEnd
)
1984 nL
= nEnd
-nTmpIndex
;
1985 pOutDev
->SetTextFillColor();
1986 aPos
.setX( rStartPos
.X() + ImpGetOutputOffset( nPara
, &rLine
, nTmpIndex
, nTmpIndex
+nL
) );
1987 pOutDev
->DrawText( aPos
, pPortion
->GetNode()->GetText(), nTmpIndex
, nEnd
-nTmpIndex
);
1994 pOutDev
->SetTextFillColor();
1995 aPos
.setX( rStartPos
.X() + ImpGetOutputOffset( nPara
, &rLine
, nTmpIndex
, nEnd
) );
1996 pOutDev
->DrawText( aPos
, pPortion
->GetNode()->GetText(), nTmpIndex
, nEnd
-nTmpIndex
);
2000 case PORTIONKIND_TAB
:
2001 // for HideSelection() only Range, pSelection = 0.
2002 if ( pSelStart
) // also implies pSelEnd
2004 const tools::Rectangle
aTabArea( aTmpPos
, Point( aTmpPos
.X()+nTxtWidth
, aTmpPos
.Y()+mnCharHeight
-1 ) );
2005 // is the Tab in the Selection???
2006 const TextPaM
aTextStart(nPara
, nIndex
);
2007 const TextPaM
aTextEnd(nPara
, nIndex
+ 1);
2008 if ((aTextStart
< *pSelEnd
) && (aTextEnd
> *pSelStart
))
2010 const Color aOldColor
= pOutDev
->GetFillColor();
2011 pOutDev
->SetFillColor(
2012 rStyleSettings
.GetHighlightColor());
2013 pOutDev
->DrawRect(aTabArea
);
2014 pOutDev
->SetFillColor(aOldColor
);
2018 pOutDev
->Erase( aTabArea
);
2023 OSL_FAIL( "ImpPaint: Unknown Portion-Type !" );
2027 nIndex
+= rTextPortion
.GetLen();
2033 if ( pPaintArea
&& ( nY
>= pPaintArea
->Bottom() ) )
2034 break; // no more visible actions
2042 if ( pPaintArea
&& ( nY
> pPaintArea
->Bottom() ) )
2043 break; // no more visible actions
2047 bool TextEngine::CreateLines( sal_uInt32 nPara
)
2049 // bool: changing Height of Paragraph Yes/No - true/false
2051 TextNode
* pNode
= mpDoc
->GetNodes()[ nPara
].get();
2052 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nPara
);
2053 SAL_WARN_IF( !pTEParaPortion
->IsInvalid(), "vcl", "CreateLines: Portion not invalid!" );
2055 const auto nOldLineCount
= pTEParaPortion
->GetLines().size();
2057 // fast special case for empty paragraphs
2058 if ( pTEParaPortion
->GetNode()->GetText().isEmpty() )
2060 if ( !pTEParaPortion
->GetTextPortions().empty() )
2061 pTEParaPortion
->GetTextPortions().Reset();
2062 pTEParaPortion
->GetLines().clear();
2063 CreateAndInsertEmptyLine( nPara
);
2064 pTEParaPortion
->SetValid();
2065 return nOldLineCount
!= pTEParaPortion
->GetLines().size();
2069 if ( pTEParaPortion
->GetLines().empty() )
2071 pTEParaPortion
->GetLines().emplace_back( );
2074 const sal_Int32 nInvalidDiff
= pTEParaPortion
->GetInvalidDiff();
2075 const sal_Int32 nInvalidStart
= pTEParaPortion
->GetInvalidPosStart();
2076 const sal_Int32 nInvalidEnd
= nInvalidStart
+ std::abs( nInvalidDiff
);
2077 bool bQuickFormat
= false;
2079 if ( pTEParaPortion
->GetWritingDirectionInfos().empty() )
2080 ImpInitWritingDirections( nPara
);
2082 if ( pTEParaPortion
->GetWritingDirectionInfos().size() == 1 && pTEParaPortion
->IsSimpleInvalid() )
2084 bQuickFormat
= nInvalidDiff
!= 0;
2085 if ( nInvalidDiff
< 0 )
2087 // check if deleting across Portion border
2089 for ( const auto & rTP
: pTEParaPortion
->GetTextPortions() )
2091 // there must be no Start/End in the deleted region
2092 nPos
+= rTP
.GetLen();
2093 if ( nPos
> nInvalidStart
&& nPos
< nInvalidEnd
)
2095 bQuickFormat
= false;
2103 RecalcTextPortion( nPara
, nInvalidStart
, nInvalidDiff
);
2105 CreateTextPortions( nPara
, nInvalidStart
);
2107 // search for line with InvalidPos; start a line prior
2108 // flag lines => do not remove!
2110 sal_uInt16 nLine
= pTEParaPortion
->GetLines().size()-1;
2111 for ( sal_uInt16 nL
= 0; nL
<= nLine
; nL
++ )
2113 TextLine
& rLine
= pTEParaPortion
->GetLines()[ nL
];
2114 if ( rLine
.GetEnd() > nInvalidStart
)
2121 // start a line before...
2122 // if typing at the end, the line before cannot change
2123 if ( nLine
&& ( !pTEParaPortion
->IsSimpleInvalid() || ( nInvalidEnd
< pNode
->GetText().getLength() ) || ( nInvalidDiff
<= 0 ) ) )
2126 TextLine
* pLine
= &( pTEParaPortion
->GetLines()[ nLine
] );
2128 // format all lines starting here
2129 std::size_t nDelFromLine
= TETextPortionList::npos
;
2131 sal_Int32 nIndex
= pLine
->GetStart();
2132 TextLine
aSaveLine( *pLine
);
2134 while ( nIndex
< pNode
->GetText().getLength() )
2137 sal_Int32 nPortionStart
= 0;
2138 sal_Int32 nPortionEnd
= 0;
2140 sal_Int32 nTmpPos
= nIndex
;
2141 std::size_t nTmpPortion
= pLine
->GetStartPortion();
2142 tools::Long nTmpWidth
= mpDoc
->GetLeftMargin();
2143 // do not subtract margin; it is included in TmpWidth
2144 tools::Long nXWidth
= std::max(
2145 mnMaxTextWidth
? mnMaxTextWidth
: std::numeric_limits
<tools::Long
>::max(), nTmpWidth
);
2147 // search for Portion that does not fit anymore into line
2148 TETextPortion
* pPortion
= nullptr;
2149 bool bBrokenLine
= false;
2151 while ( ( nTmpWidth
<= nXWidth
) && !bEOL
&& ( nTmpPortion
< pTEParaPortion
->GetTextPortions().size() ) )
2153 nPortionStart
= nTmpPos
;
2154 pPortion
= &pTEParaPortion
->GetTextPortions()[ nTmpPortion
];
2155 SAL_WARN_IF( !pPortion
->GetLen(), "vcl", "CreateLines: Empty Portion!" );
2156 if ( pNode
->GetText()[ nTmpPos
] == '\t' )
2158 tools::Long nCurPos
= nTmpWidth
-mpDoc
->GetLeftMargin();
2159 nTmpWidth
= ((nCurPos
/mnDefTab
)+1)*mnDefTab
+mpDoc
->GetLeftMargin();
2160 pPortion
->GetWidth() = nTmpWidth
- nCurPos
- mpDoc
->GetLeftMargin();
2161 // infinite loop, if this is the first token of the line and nTmpWidth > aPaperSize.Width !!!
2162 if ( ( nTmpWidth
>= nXWidth
) && ( nTmpPortion
== pLine
->GetStartPortion() ) )
2165 pPortion
->GetWidth() = nXWidth
-1;
2166 nTmpWidth
= pPortion
->GetWidth();
2170 pPortion
->GetKind() = PORTIONKIND_TAB
;
2175 pPortion
->GetWidth() = CalcTextWidth( nPara
, nTmpPos
, pPortion
->GetLen() );
2176 nTmpWidth
+= pPortion
->GetWidth();
2178 pPortion
->SetRightToLeft( ImpGetRightToLeft( nPara
, nTmpPos
+1 ) );
2179 pPortion
->GetKind() = PORTIONKIND_TEXT
;
2182 nTmpPos
+= pPortion
->GetLen();
2183 nPortionEnd
= nTmpPos
;
2187 // this was perhaps one Portion too far
2188 bool bFixedEnd
= false;
2189 if ( nTmpWidth
> nXWidth
)
2193 nPortionEnd
= nTmpPos
;
2194 nTmpPos
-= pPortion
->GetLen();
2195 nPortionStart
= nTmpPos
;
2199 nTmpWidth
-= pPortion
->GetWidth();
2200 if ( pPortion
->GetKind() == PORTIONKIND_TAB
)
2209 pLine
->SetEnd( nPortionEnd
);
2210 OSL_ENSURE(pTEParaPortion
->GetTextPortions().size(),
2211 "CreateLines: No TextPortions?");
2212 pLine
->SetEndPortion( pTEParaPortion
->GetTextPortions().size() - 1 );
2217 pLine
->SetEnd( nPortionStart
);
2218 pLine
->SetEndPortion( nTmpPortion
-1 );
2220 else if ( bBrokenLine
)
2222 pLine
->SetEnd( nPortionStart
+1 );
2223 pLine
->SetEndPortion( nTmpPortion
-1 );
2227 SAL_WARN_IF( (nPortionEnd
-nPortionStart
) != pPortion
->GetLen(), "vcl", "CreateLines: There is a Portion after all?!" );
2228 const tools::Long nRemainingWidth
= mnMaxTextWidth
- nTmpWidth
;
2229 ImpBreakLine( nPara
, pLine
, nPortionStart
, nRemainingWidth
);
2232 if ( ( ImpGetAlign() == TxtAlign::Center
) || ( ImpGetAlign() == TxtAlign::Right
) )
2235 tools::Long nTextWidth
= 0;
2236 for ( std::size_t nTP
= pLine
->GetStartPortion(); nTP
<= pLine
->GetEndPortion(); nTP
++ )
2238 TETextPortion
& rTextPortion
= pTEParaPortion
->GetTextPortions()[ nTP
];
2239 nTextWidth
+= rTextPortion
.GetWidth();
2241 const tools::Long nSpace
= mnMaxTextWidth
- nTextWidth
;
2244 if ( ImpGetAlign() == TxtAlign::Center
)
2245 pLine
->SetStartX( static_cast<sal_uInt16
>(nSpace
/ 2) );
2246 else // TxtAlign::Right
2247 pLine
->SetStartX( static_cast<sal_uInt16
>(nSpace
) );
2252 pLine
->SetStartX( mpDoc
->GetLeftMargin() );
2255 // check if the line has to be printed again
2256 pLine
->SetInvalid();
2258 if ( pTEParaPortion
->IsSimpleInvalid() )
2260 // Change due to simple TextChange...
2261 // Do not abort formatting, as Portions might have to be split!
2262 // Once it is ok to abort, then validate the following lines!
2263 // But mark as valid, thus reduce printing...
2264 if ( pLine
->GetEnd() < nInvalidStart
)
2266 if ( *pLine
== aSaveLine
)
2273 const sal_Int32 nStart
= pLine
->GetStart();
2274 const sal_Int32 nEnd
= pLine
->GetEnd();
2276 if ( nStart
> nInvalidEnd
)
2278 if ( ( ( nStart
-nInvalidDiff
) == aSaveLine
.GetStart() ) &&
2279 ( ( nEnd
-nInvalidDiff
) == aSaveLine
.GetEnd() ) )
2284 pTEParaPortion
->CorrectValuesBehindLastFormattedLine( nLine
);
2289 else if ( bQuickFormat
&& ( nEnd
> nInvalidEnd
) )
2291 // If the invalid line ends such that the next line starts
2292 // at the 'same' position as before (no change in line breaks),
2293 // the text width does not have to be recalculated.
2294 if ( nEnd
== ( aSaveLine
.GetEnd() + nInvalidDiff
) )
2296 pTEParaPortion
->CorrectValuesBehindLastFormattedLine( nLine
);
2303 nIndex
= pLine
->GetEnd(); // next line Start = previous line End
2304 // because nEnd is past the last char!
2306 const std::size_t nEndPortion
= pLine
->GetEndPortion();
2308 // next line or new line
2310 if ( nLine
< pTEParaPortion
->GetLines().size()-1 )
2311 pLine
= &( pTEParaPortion
->GetLines()[ ++nLine
] );
2312 if ( pLine
&& ( nIndex
>= pNode
->GetText().getLength() ) )
2314 nDelFromLine
= nLine
;
2319 if ( nIndex
< pNode
->GetText().getLength() )
2322 pTEParaPortion
->GetLines().insert( pTEParaPortion
->GetLines().begin() + nLine
, TextLine() );
2323 pLine
= &pTEParaPortion
->GetLines()[nLine
];
2331 pLine
->SetStart( nIndex
);
2332 pLine
->SetEnd( nIndex
);
2333 pLine
->SetStartPortion( nEndPortion
+1 );
2334 pLine
->SetEndPortion( nEndPortion
+1 );
2336 } // while ( Index < Len )
2338 if (nDelFromLine
!= TETextPortionList::npos
)
2340 pTEParaPortion
->GetLines().erase( pTEParaPortion
->GetLines().begin() + nDelFromLine
,
2341 pTEParaPortion
->GetLines().end() );
2344 SAL_WARN_IF( pTEParaPortion
->GetLines().empty(), "vcl", "CreateLines: No Line!" );
2346 pTEParaPortion
->SetValid();
2348 return nOldLineCount
!= pTEParaPortion
->GetLines().size();
2351 OUString
TextEngine::GetWord( const TextPaM
& rCursorPos
, TextPaM
* pStartOfWord
, TextPaM
* pEndOfWord
)
2354 if ( rCursorPos
.GetPara() < mpDoc
->GetNodes().size() )
2356 TextSelection
aSel( rCursorPos
);
2357 TextNode
* pNode
= mpDoc
->GetNodes()[ rCursorPos
.GetPara() ].get();
2358 uno::Reference
< i18n::XBreakIterator
> xBI
= GetBreakIterator();
2359 i18n::Boundary aBoundary
= xBI
->getWordBoundary( pNode
->GetText(), rCursorPos
.GetIndex(), GetLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES
, true );
2360 // tdf#57879 - expand selection to the left to include connector punctuations and search for additional word boundaries
2361 if (aBoundary
.startPos
> 0 && aBoundary
.startPos
< pNode
->GetText().getLength() && u_charType(pNode
->GetText()[aBoundary
.startPos
]) == U_CONNECTOR_PUNCTUATION
)
2363 aBoundary
.startPos
= xBI
->getWordBoundary(pNode
->GetText(), aBoundary
.startPos
- 1,
2364 GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES
, true).startPos
;
2366 while (aBoundary
.startPos
> 0 && u_charType(pNode
->GetText()[aBoundary
.startPos
- 1]) == U_CONNECTOR_PUNCTUATION
)
2368 aBoundary
.startPos
= std::min(aBoundary
.startPos
,
2369 xBI
->getWordBoundary( pNode
->GetText(), aBoundary
.startPos
- 2,
2370 GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES
, true).startPos
);
2372 // tdf#57879 - expand selection to the right to include connector punctuations and search for additional word boundaries
2373 if (aBoundary
.endPos
> 0 && aBoundary
.endPos
< pNode
->GetText().getLength() && u_charType(pNode
->GetText()[aBoundary
.endPos
- 1]) == U_CONNECTOR_PUNCTUATION
)
2375 aBoundary
.endPos
= xBI
->getWordBoundary(pNode
->GetText(), aBoundary
.endPos
,
2376 GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES
, true).endPos
;
2378 while (aBoundary
.endPos
< pNode
->GetText().getLength() && u_charType(pNode
->GetText()[aBoundary
.endPos
]) == U_CONNECTOR_PUNCTUATION
)
2380 aBoundary
.endPos
= xBI
->getWordBoundary(pNode
->GetText(), aBoundary
.endPos
+ 1,
2381 GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES
, true).endPos
;
2383 aSel
.GetStart().GetIndex() = aBoundary
.startPos
;
2384 aSel
.GetEnd().GetIndex() = aBoundary
.endPos
;
2385 aWord
= pNode
->GetText().copy( aSel
.GetStart().GetIndex(), aSel
.GetEnd().GetIndex() - aSel
.GetStart().GetIndex() );
2387 *pStartOfWord
= aSel
.GetStart();
2389 *pEndOfWord
= aSel
.GetEnd();
2394 bool TextEngine::Read( SvStream
& rInput
, const TextSelection
* pSel
)
2396 const bool bUpdate
= GetUpdateMode();
2397 SetUpdateMode( false );
2405 const sal_uInt32 nParas
= static_cast<sal_uInt32
>(mpDoc
->GetNodes().size());
2406 TextNode
* pNode
= mpDoc
->GetNodes()[ nParas
- 1 ].get();
2407 aSel
= TextPaM( nParas
-1 , pNode
->GetText().getLength() );
2410 if ( aSel
.HasRange() )
2411 aSel
= ImpDeleteText( aSel
);
2413 OStringBuffer aLine
;
2414 bool bDone
= rInput
.ReadLine( aLine
);
2415 OUString
aTmpStr(OStringToOUString(aLine
, rInput
.GetStreamCharSet()));
2418 aSel
= ImpInsertText( aSel
, aTmpStr
);
2419 bDone
= rInput
.ReadLine( aLine
);
2420 aTmpStr
= OStringToOUString(aLine
, rInput
.GetStreamCharSet());
2422 aSel
= ImpInsertParaBreak( aSel
.GetEnd() );
2427 const TextSelection
aNewSel( aSel
.GetEnd(), aSel
.GetEnd() );
2429 // so that FormatAndUpdate does not access the invalid selection
2430 if ( GetActiveView() )
2431 GetActiveView()->ImpSetSelection( aNewSel
);
2433 SetUpdateMode( bUpdate
);
2434 FormatAndUpdate( GetActiveView() );
2436 return rInput
.GetError() == ERRCODE_NONE
;
2439 void TextEngine::Write( SvStream
& rOutput
)
2442 const sal_uInt32 nParas
= static_cast<sal_uInt32
>(mpDoc
->GetNodes().size());
2443 TextNode
* pSelNode
= mpDoc
->GetNodes()[ nParas
- 1 ].get();
2444 aSel
.GetStart() = TextPaM( 0, 0 );
2445 aSel
.GetEnd() = TextPaM( nParas
-1, pSelNode
->GetText().getLength() );
2447 for ( sal_uInt32 nPara
= aSel
.GetStart().GetPara(); nPara
<= aSel
.GetEnd().GetPara(); ++nPara
)
2449 TextNode
* pNode
= mpDoc
->GetNodes()[ nPara
].get();
2451 const sal_Int32 nStartPos
= nPara
== aSel
.GetStart().GetPara()
2452 ? aSel
.GetStart().GetIndex() : 0;
2453 const sal_Int32 nEndPos
= nPara
== aSel
.GetEnd().GetPara()
2454 ? aSel
.GetEnd().GetIndex() : pNode
->GetText().getLength();
2456 const OUString aText
= pNode
->GetText().copy( nStartPos
, nEndPos
-nStartPos
);
2457 rOutput
.WriteLine(OUStringToOString(aText
, rOutput
.GetStreamCharSet()));
2461 void TextEngine::RemoveAttribs( sal_uInt32 nPara
)
2463 if ( nPara
>= mpDoc
->GetNodes().size() )
2466 TextNode
* pNode
= mpDoc
->GetNodes()[ nPara
].get();
2467 if ( pNode
->GetCharAttribs().Count() )
2469 pNode
->GetCharAttribs().Clear();
2471 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nPara
);
2472 pTEParaPortion
->MarkSelectionInvalid( 0 );
2474 mbFormatted
= false;
2476 IdleFormatAndUpdate( nullptr, 0xFFFF );
2480 void TextEngine::SetAttrib( const TextAttrib
& rAttr
, sal_uInt32 nPara
, sal_Int32 nStart
, sal_Int32 nEnd
)
2483 // For now do not check if Attributes overlap!
2484 // This function is for TextEditors that want to _quickly_ generate the Syntax-Highlight
2486 // As TextEngine is currently intended only for TextEditors, there is no Undo for Attributes!
2488 if ( nPara
>= mpDoc
->GetNodes().size() )
2491 TextNode
* pNode
= mpDoc
->GetNodes()[ nPara
].get();
2492 TEParaPortion
* pTEParaPortion
= mpTEParaPortions
->GetObject( nPara
);
2494 const sal_Int32 nMax
= pNode
->GetText().getLength();
2495 if ( nStart
> nMax
)
2500 pNode
->GetCharAttribs().InsertAttrib( std::make_unique
<TextCharAttrib
>( rAttr
, nStart
, nEnd
) );
2501 pTEParaPortion
->MarkSelectionInvalid( nStart
);
2503 mbFormatted
= false;
2504 IdleFormatAndUpdate( nullptr, 0xFFFF );
2507 void TextEngine::SetTextAlign( TxtAlign eAlign
)
2509 if ( eAlign
!= meAlign
)
2517 void TextEngine::ValidateSelection( TextSelection
& rSel
) const
2519 ValidatePaM( rSel
.GetStart() );
2520 ValidatePaM( rSel
.GetEnd() );
2523 void TextEngine::ValidatePaM( TextPaM
& rPaM
) const
2525 const sal_uInt32 nParas
= static_cast<sal_uInt32
>(mpDoc
->GetNodes().size());
2526 if ( rPaM
.GetPara() >= nParas
)
2528 rPaM
.GetPara() = nParas
? nParas
-1 : 0;
2529 rPaM
.GetIndex() = TEXT_INDEX_ALL
;
2532 const sal_Int32 nMaxIndex
= GetTextLen( rPaM
.GetPara() );
2533 if ( rPaM
.GetIndex() > nMaxIndex
)
2534 rPaM
.GetIndex() = nMaxIndex
;
2537 // adjust State & Selection
2539 void TextEngine::ImpParagraphInserted( sal_uInt32 nPara
)
2541 // No adjustment needed for the active View;
2542 // but for all passive Views the Selection needs adjusting.
2543 if ( mpViews
->size() > 1 )
2545 for ( auto nView
= mpViews
->size(); nView
; )
2547 TextView
* pView
= (*mpViews
)[ --nView
];
2548 if ( pView
!= GetActiveView() )
2550 for ( int n
= 0; n
<= 1; n
++ )
2552 TextPaM
& rPaM
= n
? pView
->GetSelection().GetStart(): pView
->GetSelection().GetEnd();
2553 if ( rPaM
.GetPara() >= nPara
)
2559 Broadcast( TextHint( SfxHintId::TextParaInserted
, nPara
) );
2562 void TextEngine::ImpParagraphRemoved( sal_uInt32 nPara
)
2564 if ( mpViews
->size() > 1 )
2566 for ( auto nView
= mpViews
->size(); nView
; )
2568 TextView
* pView
= (*mpViews
)[ --nView
];
2569 if ( pView
!= GetActiveView() )
2571 const sal_uInt32 nParas
= static_cast<sal_uInt32
>(mpDoc
->GetNodes().size());
2572 for ( int n
= 0; n
<= 1; n
++ )
2574 TextPaM
& rPaM
= n
? pView
->GetSelection().GetStart(): pView
->GetSelection().GetEnd();
2575 if ( rPaM
.GetPara() > nPara
)
2577 else if ( rPaM
.GetPara() == nPara
)
2579 rPaM
.GetIndex() = 0;
2580 if ( rPaM
.GetPara() >= nParas
)
2587 Broadcast( TextHint( SfxHintId::TextParaRemoved
, nPara
) );
2590 void TextEngine::ImpCharsRemoved( sal_uInt32 nPara
, sal_Int32 nPos
, sal_Int32 nChars
)
2592 if ( mpViews
->size() > 1 )
2594 for ( auto nView
= mpViews
->size(); nView
; )
2596 TextView
* pView
= (*mpViews
)[ --nView
];
2597 if ( pView
!= GetActiveView() )
2599 const sal_Int32 nEnd
= nPos
+ nChars
;
2600 for ( int n
= 0; n
<= 1; n
++ )
2602 TextPaM
& rPaM
= n
? pView
->GetSelection().GetStart(): pView
->GetSelection().GetEnd();
2603 if ( rPaM
.GetPara() == nPara
)
2605 if ( rPaM
.GetIndex() > nEnd
)
2606 rPaM
.GetIndex() = rPaM
.GetIndex() - nChars
;
2607 else if ( rPaM
.GetIndex() > nPos
)
2608 rPaM
.GetIndex() = nPos
;
2614 Broadcast( TextHint( SfxHintId::TextParaContentChanged
, nPara
) );
2617 void TextEngine::ImpCharsInserted( sal_uInt32 nPara
, sal_Int32 nPos
, sal_Int32 nChars
)
2619 if ( mpViews
->size() > 1 )
2621 for ( auto nView
= mpViews
->size(); nView
; )
2623 TextView
* pView
= (*mpViews
)[ --nView
];
2624 if ( pView
!= GetActiveView() )
2626 for ( int n
= 0; n
<= 1; n
++ )
2628 TextPaM
& rPaM
= n
? pView
->GetSelection().GetStart(): pView
->GetSelection().GetEnd();
2629 if ( rPaM
.GetPara() == nPara
)
2631 if ( rPaM
.GetIndex() >= nPos
)
2632 rPaM
.GetIndex() += nChars
;
2638 Broadcast( TextHint( SfxHintId::TextParaContentChanged
, nPara
) );
2641 void TextEngine::Draw( OutputDevice
* pDev
, const Point
& rPos
)
2643 ImpPaint( pDev
, rPos
, nullptr );
2646 void TextEngine::SetLeftMargin( sal_uInt16 n
)
2648 mpDoc
->SetLeftMargin( n
);
2651 uno::Reference
< i18n::XBreakIterator
> const & TextEngine::GetBreakIterator()
2653 if ( !mxBreakIterator
.is() )
2654 mxBreakIterator
= vcl::unohelper::CreateBreakIterator();
2655 SAL_WARN_IF( !mxBreakIterator
.is(), "vcl", "BreakIterator: Failed to create!" );
2656 return mxBreakIterator
;
2659 void TextEngine::SetLocale( const css::lang::Locale
& rLocale
)
2662 mpLocaleDataWrapper
.reset();
2665 css::lang::Locale
const & TextEngine::GetLocale()
2667 if ( maLocale
.Language
.isEmpty() )
2669 maLocale
= Application::GetSettings().GetUILanguageTag().getLocale(); // TODO: why UI locale?
2674 LocaleDataWrapper
* TextEngine::ImpGetLocaleDataWrapper()
2676 if ( !mpLocaleDataWrapper
)
2677 mpLocaleDataWrapper
.reset( new LocaleDataWrapper( LanguageTag( GetLocale()) ) );
2679 return mpLocaleDataWrapper
.get();
2682 void TextEngine::SetRightToLeft( bool bR2L
)
2684 if ( mbRightToLeft
!= bR2L
)
2686 mbRightToLeft
= bR2L
;
2687 meAlign
= bR2L
? TxtAlign::Right
: TxtAlign::Left
;
2693 void TextEngine::ImpInitWritingDirections( sal_uInt32 nPara
)
2695 TEParaPortion
* pParaPortion
= mpTEParaPortions
->GetObject( nPara
);
2696 std::vector
<TEWritingDirectionInfo
>& rInfos
= pParaPortion
->GetWritingDirectionInfos();
2699 if ( !pParaPortion
->GetNode()->GetText().isEmpty() )
2701 const UBiDiLevel nBidiLevel
= IsRightToLeft() ? 1 /*RTL*/ : 0 /*LTR*/;
2702 OUString
aText( pParaPortion
->GetNode()->GetText() );
2704 // Bidi functions from icu 2.0
2706 UErrorCode nError
= U_ZERO_ERROR
;
2707 UBiDi
* pBidi
= ubidi_openSized( aText
.getLength(), 0, &nError
);
2708 nError
= U_ZERO_ERROR
;
2710 ubidi_setPara( pBidi
, reinterpret_cast<const UChar
*>(aText
.getStr()), aText
.getLength(), nBidiLevel
, nullptr, &nError
);
2711 nError
= U_ZERO_ERROR
;
2713 tools::Long nCount
= ubidi_countRuns( pBidi
, &nError
);
2717 UBiDiLevel nCurrDir
;
2719 for ( tools::Long nIdx
= 0; nIdx
< nCount
; ++nIdx
)
2721 ubidi_getLogicalRun( pBidi
, nStart
, &nEnd
, &nCurrDir
);
2722 // bit 0 of nCurrDir indicates direction
2723 rInfos
.emplace_back( /*bLeftToRight*/ nCurrDir
% 2 == 0, nStart
, nEnd
);
2727 ubidi_close( pBidi
);
2730 // No infos mean no CTL and default dir is L2R...
2731 if ( rInfos
.empty() )
2732 rInfos
.emplace_back( 0, 0, pParaPortion
->GetNode()->GetText().getLength() );
2736 bool TextEngine::ImpGetRightToLeft( sal_uInt32 nPara
, sal_Int32 nPos
)
2738 bool bRightToLeft
= false;
2740 TextNode
* pNode
= mpDoc
->GetNodes()[ nPara
].get();
2741 if ( pNode
&& !pNode
->GetText().isEmpty() )
2743 TEParaPortion
* pParaPortion
= mpTEParaPortions
->GetObject( nPara
);
2744 if ( pParaPortion
->GetWritingDirectionInfos().empty() )
2745 ImpInitWritingDirections( nPara
);
2747 std::vector
<TEWritingDirectionInfo
>& rDirInfos
= pParaPortion
->GetWritingDirectionInfos();
2748 for ( const auto& rWritingDirectionInfo
: rDirInfos
)
2750 if ( rWritingDirectionInfo
.nStartPos
<= nPos
&& rWritingDirectionInfo
.nEndPos
>= nPos
)
2752 bRightToLeft
= !rWritingDirectionInfo
.bLeftToRight
;
2757 return bRightToLeft
;
2760 tools::Long
TextEngine::ImpGetPortionXOffset( sal_uInt32 nPara
, TextLine
const * pLine
, std::size_t nTextPortion
)
2762 tools::Long nX
= pLine
->GetStartX();
2764 TEParaPortion
* pParaPortion
= mpTEParaPortions
->GetObject( nPara
);
2766 for ( std::size_t i
= pLine
->GetStartPortion(); i
< nTextPortion
; i
++ )
2768 TETextPortion
& rPortion
= pParaPortion
->GetTextPortions()[ i
];
2769 nX
+= rPortion
.GetWidth();
2772 TETextPortion
& rDestPortion
= pParaPortion
->GetTextPortions()[ nTextPortion
];
2773 if ( rDestPortion
.GetKind() != PORTIONKIND_TAB
)
2775 if ( !IsRightToLeft() && rDestPortion
.IsRightToLeft() )
2777 // Portions behind must be added, visual before this portion
2778 std::size_t nTmpPortion
= nTextPortion
+1;
2779 while ( nTmpPortion
<= pLine
->GetEndPortion() )
2781 TETextPortion
& rNextTextPortion
= pParaPortion
->GetTextPortions()[ nTmpPortion
];
2782 if ( rNextTextPortion
.IsRightToLeft() && ( rNextTextPortion
.GetKind() != PORTIONKIND_TAB
) )
2783 nX
+= rNextTextPortion
.GetWidth();
2788 // Portions before must be removed, visual behind this portion
2789 nTmpPortion
= nTextPortion
;
2790 while ( nTmpPortion
> pLine
->GetStartPortion() )
2793 TETextPortion
& rPrevTextPortion
= pParaPortion
->GetTextPortions()[ nTmpPortion
];
2794 if ( rPrevTextPortion
.IsRightToLeft() && ( rPrevTextPortion
.GetKind() != PORTIONKIND_TAB
) )
2795 nX
-= rPrevTextPortion
.GetWidth();
2800 else if ( IsRightToLeft() && !rDestPortion
.IsRightToLeft() )
2802 // Portions behind must be removed, visual behind this portion
2803 std::size_t nTmpPortion
= nTextPortion
+1;
2804 while ( nTmpPortion
<= pLine
->GetEndPortion() )
2806 TETextPortion
& rNextTextPortion
= pParaPortion
->GetTextPortions()[ nTmpPortion
];
2807 if ( !rNextTextPortion
.IsRightToLeft() && ( rNextTextPortion
.GetKind() != PORTIONKIND_TAB
) )
2808 nX
+= rNextTextPortion
.GetWidth();
2813 // Portions before must be added, visual before this portion
2814 nTmpPortion
= nTextPortion
;
2815 while ( nTmpPortion
> pLine
->GetStartPortion() )
2818 TETextPortion
& rPrevTextPortion
= pParaPortion
->GetTextPortions()[ nTmpPortion
];
2819 if ( !rPrevTextPortion
.IsRightToLeft() && ( rPrevTextPortion
.GetKind() != PORTIONKIND_TAB
) )
2820 nX
-= rPrevTextPortion
.GetWidth();
2830 void TextEngine::ImpInitLayoutMode( OutputDevice
* pOutDev
)
2832 vcl::text::ComplexTextLayoutFlags nLayoutMode
= pOutDev
->GetLayoutMode();
2834 nLayoutMode
&= ~vcl::text::ComplexTextLayoutFlags(vcl::text::ComplexTextLayoutFlags::BiDiRtl
| vcl::text::ComplexTextLayoutFlags::BiDiStrong
);
2836 pOutDev
->SetLayoutMode( nLayoutMode
);
2839 TxtAlign
TextEngine::ImpGetAlign() const
2841 TxtAlign eAlign
= meAlign
;
2842 if ( IsRightToLeft() )
2844 if ( eAlign
== TxtAlign::Left
)
2845 eAlign
= TxtAlign::Right
;
2846 else if ( eAlign
== TxtAlign::Right
)
2847 eAlign
= TxtAlign::Left
;
2852 tools::Long
TextEngine::ImpGetOutputOffset( sal_uInt32 nPara
, TextLine
* pLine
, sal_Int32 nIndex
, sal_Int32 nIndex2
)
2854 TEParaPortion
* pPortion
= mpTEParaPortions
->GetObject( nPara
);
2856 sal_Int32 nPortionStart
{0};
2857 const std::size_t nPortion
= pPortion
->GetTextPortions().FindPortion( nIndex
, nPortionStart
, true );
2859 TETextPortion
& rTextPortion
= pPortion
->GetTextPortions()[ nPortion
];
2863 if ( ( nIndex
== nPortionStart
) && ( nIndex
== nIndex2
) )
2865 // Output of full portion, so we need portion x offset.
2866 // Use ImpGetPortionXOffset, because GetXPos may deliver left or right position from portion, depending on R2L, L2R
2867 nX
= ImpGetPortionXOffset( nPara
, pLine
, nPortion
);
2868 if ( IsRightToLeft() )
2870 nX
= -nX
- rTextPortion
.GetWidth();
2875 nX
= ImpGetXPos( nPara
, pLine
, nIndex
, nIndex
== nPortionStart
);
2876 if ( nIndex2
!= nIndex
)
2878 const tools::Long nX2
= ImpGetXPos( nPara
, pLine
, nIndex2
);
2879 if ( ( !IsRightToLeft() && ( nX2
< nX
) ) ||
2880 ( IsRightToLeft() && ( nX2
> nX
) ) )
2885 if ( IsRightToLeft() )
2894 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */