summaryrefslogtreecommitdiffstats
path: root/vcl/source/edit/texteng.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/source/edit/texteng.cxx')
-rw-r--r--vcl/source/edit/texteng.cxx2895
1 files changed, 2895 insertions, 0 deletions
diff --git a/vcl/source/edit/texteng.cxx b/vcl/source/edit/texteng.cxx
new file mode 100644
index 000000000..6171e0417
--- /dev/null
+++ b/vcl/source/edit/texteng.cxx
@@ -0,0 +1,2895 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/stream.hxx>
+
+#include <vcl/texteng.hxx>
+#include <vcl/textview.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/inputctx.hxx>
+#include "textdoc.hxx"
+#include "textdat2.hxx"
+#include "textundo.hxx"
+#include "textund2.hxx"
+#include <svl/ctloptions.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/virdev.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+
+#include <com/sun/star/i18n/WordType.hpp>
+
+#include <com/sun/star/i18n/InputSequenceChecker.hpp>
+#include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+
+#include <comphelper/processfactory.hxx>
+
+#include <unotools/localedatawrapper.hxx>
+#include <vcl/unohelp.hxx>
+
+#include <vcl/svapp.hxx>
+
+#include <unicode/ubidi.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdlib>
+#include <memory>
+#include <o3tl/sorted_vector.hxx>
+#include <string_view>
+#include <vector>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+TextEngine::TextEngine()
+ : mpActiveView {nullptr}
+ , maTextColor {COL_BLACK}
+ , mnMaxTextLen {0}
+ , mnMaxTextWidth {0}
+ , mnCharHeight {0}
+ , mnCurTextWidth {-1}
+ , mnCurTextHeight {0}
+ , mnDefTab {0}
+ , meAlign {TxtAlign::Left}
+ , mbIsFormatting {false}
+ , mbFormatted {false}
+ , mbUpdate {true}
+ , mbModified {false}
+ , mbUndoEnabled {false}
+ , mbIsInUndo {false}
+ , mbDowning {false}
+ , mbRightToLeft {false}
+ , mbHasMultiLineParas {false}
+{
+ mpViews.reset( new TextViews );
+
+ mpIdleFormatter.reset( new IdleFormatter );
+ mpIdleFormatter->SetInvokeHandler( LINK( this, TextEngine, IdleFormatHdl ) );
+
+ mpRefDev = VclPtr<VirtualDevice>::Create();
+
+ ImpInitLayoutMode( mpRefDev );
+
+ ImpInitDoc();
+
+ vcl::Font aFont(mpRefDev->GetFont().GetFamilyName(), Size(0, 0));
+ aFont.SetTransparent( false );
+ Color aFillColor( aFont.GetFillColor() );
+ aFillColor.SetAlpha( 255 );
+ aFont.SetFillColor( aFillColor );
+ SetFont( aFont );
+}
+
+TextEngine::~TextEngine()
+{
+ mbDowning = true;
+
+ mpIdleFormatter.reset();
+ mpDoc.reset();
+ mpTEParaPortions.reset();
+ mpViews.reset(); // only the list, not the Views
+ mpRefDev.disposeAndClear();
+ mpUndoManager.reset();
+ mpIMEInfos.reset();
+ mpLocaleDataWrapper.reset();
+}
+
+void TextEngine::InsertView( TextView* pTextView )
+{
+ mpViews->push_back( pTextView );
+ pTextView->SetSelection( TextSelection() );
+
+ if ( !GetActiveView() )
+ SetActiveView( pTextView );
+}
+
+void TextEngine::RemoveView( TextView* pTextView )
+{
+ TextViews::iterator it = std::find( mpViews->begin(), mpViews->end(), pTextView );
+ if( it != mpViews->end() )
+ {
+ pTextView->HideCursor();
+ mpViews->erase( it );
+ if ( pTextView == GetActiveView() )
+ SetActiveView( nullptr );
+ }
+}
+
+sal_uInt16 TextEngine::GetViewCount() const
+{
+ return mpViews->size();
+}
+
+TextView* TextEngine::GetView( sal_uInt16 nView ) const
+{
+ return (*mpViews)[ nView ];
+}
+
+
+void TextEngine::SetActiveView( TextView* pTextView )
+{
+ if ( pTextView != mpActiveView )
+ {
+ if ( mpActiveView )
+ mpActiveView->HideSelection();
+
+ mpActiveView = pTextView;
+
+ if ( mpActiveView )
+ mpActiveView->ShowSelection();
+ }
+}
+
+void TextEngine::SetFont( const vcl::Font& rFont )
+{
+ if ( rFont == maFont )
+ return;
+
+ maFont = rFont;
+ // #i40221# As the font's color now defaults to transparent (since i35764)
+ // we have to choose a useful textcolor in this case.
+ // Otherwise maTextColor and maFont.GetColor() are both transparent...
+ if( rFont.GetColor() == COL_TRANSPARENT )
+ maTextColor = COL_BLACK;
+ else
+ maTextColor = rFont.GetColor();
+
+ // Do not allow transparent fonts because of selection
+ // (otherwise delete the background in ImplPaint later differently)
+ maFont.SetTransparent( false );
+ // Tell VCL not to use the font color, use text color from OutputDevice
+ maFont.SetColor( COL_TRANSPARENT );
+ Color aFillColor( maFont.GetFillColor() );
+ aFillColor.SetAlpha( 255 );
+ maFont.SetFillColor( aFillColor );
+
+ maFont.SetAlignment( ALIGN_TOP );
+ mpRefDev->SetFont( maFont );
+ mnDefTab = mpRefDev->GetTextWidth(" ");
+ if ( !mnDefTab )
+ mnDefTab = mpRefDev->GetTextWidth("XXXX");
+ if ( !mnDefTab )
+ mnDefTab = 1;
+ mnCharHeight = mpRefDev->GetTextHeight();
+
+ FormatFullDoc();
+ UpdateViews();
+
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ pView->GetWindow()->SetInputContext( InputContext( GetFont(), !pView->IsReadOnly() ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
+ }
+
+}
+
+void TextEngine::SetMaxTextLen( sal_Int32 nLen )
+{
+ mnMaxTextLen = nLen>=0 ? nLen : EDIT_NOLIMIT;
+}
+
+void TextEngine::SetMaxTextWidth( tools::Long nMaxWidth )
+{
+ if ( nMaxWidth>=0 && nMaxWidth != mnMaxTextWidth )
+ {
+ mnMaxTextWidth = nMaxWidth;
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+const sal_Unicode static_aLFText[] = { '\n', 0 };
+const sal_Unicode static_aCRText[] = { '\r', 0 };
+const sal_Unicode static_aCRLFText[] = { '\r', '\n', 0 };
+
+static const sal_Unicode* static_getLineEndText( LineEnd aLineEnd )
+{
+ const sal_Unicode* pRet = nullptr;
+
+ switch( aLineEnd )
+ {
+ case LINEEND_LF:
+ pRet = static_aLFText;
+ break;
+ case LINEEND_CR:
+ pRet = static_aCRText;
+ break;
+ case LINEEND_CRLF:
+ pRet = static_aCRLFText;
+ break;
+ }
+ return pRet;
+}
+
+void TextEngine::ReplaceText(const TextSelection& rSel, const OUString& rText)
+{
+ ImpInsertText( rSel, rText );
+}
+
+OUString TextEngine::GetText( LineEnd aSeparator ) const
+{
+ return mpDoc->GetText( static_getLineEndText( aSeparator ) );
+}
+
+OUString TextEngine::GetTextLines( LineEnd aSeparator ) const
+{
+ OUStringBuffer aText;
+ const sal_uInt32 nParas = mpTEParaPortions->Count();
+ const sal_Unicode* pSep = static_getLineEndText( aSeparator );
+ for ( sal_uInt32 nP = 0; nP < nParas; ++nP )
+ {
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nP );
+
+ const size_t nLines = pTEParaPortion->GetLines().size();
+ for ( size_t nL = 0; nL < nLines; ++nL )
+ {
+ TextLine& rLine = pTEParaPortion->GetLines()[nL];
+ aText.append( pTEParaPortion->GetNode()->GetText().subView(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()) );
+ if ( pSep && ( ( (nP+1) < nParas ) || ( (nL+1) < nLines ) ) )
+ aText.append(pSep);
+ }
+ }
+ return aText.makeStringAndClear();
+}
+
+OUString TextEngine::GetText( sal_uInt32 nPara ) const
+{
+ return mpDoc->GetText( nPara );
+}
+
+sal_Int32 TextEngine::GetTextLen() const
+{
+ return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ) );
+}
+
+sal_Int32 TextEngine::GetTextLen( const TextSelection& rSel ) const
+{
+ TextSelection aSel( rSel );
+ aSel.Justify();
+ ValidateSelection( aSel );
+ return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ), &aSel );
+}
+
+sal_Int32 TextEngine::GetTextLen( const sal_uInt32 nPara ) const
+{
+ return mpDoc->GetNodes()[ nPara ]->GetText().getLength();
+}
+
+void TextEngine::SetUpdateMode( bool bUpdate )
+{
+ if ( bUpdate != mbUpdate )
+ {
+ mbUpdate = bUpdate;
+ if ( mbUpdate )
+ {
+ FormatAndUpdate( GetActiveView() );
+ if ( GetActiveView() )
+ GetActiveView()->ShowCursor();
+ }
+ }
+}
+
+bool TextEngine::DoesKeyChangeText( const KeyEvent& rKeyEvent )
+{
+ bool bDoesChange = false;
+
+ KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
+ if ( eFunc != KeyFuncType::DONTKNOW )
+ {
+ switch ( eFunc )
+ {
+ case KeyFuncType::UNDO:
+ case KeyFuncType::REDO:
+ case KeyFuncType::CUT:
+ case KeyFuncType::PASTE:
+ bDoesChange = true;
+ break;
+ default:
+ // might get handled below
+ eFunc = KeyFuncType::DONTKNOW;
+ }
+ }
+ if ( eFunc == KeyFuncType::DONTKNOW )
+ {
+ switch ( rKeyEvent.GetKeyCode().GetCode() )
+ {
+ case KEY_DELETE:
+ case KEY_BACKSPACE:
+ if ( !rKeyEvent.GetKeyCode().IsMod2() )
+ bDoesChange = true;
+ break;
+ case KEY_RETURN:
+ case KEY_TAB:
+ if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() )
+ bDoesChange = true;
+ break;
+ default:
+ bDoesChange = TextEngine::IsSimpleCharInput( rKeyEvent );
+ }
+ }
+ return bDoesChange;
+}
+
+bool TextEngine::IsSimpleCharInput( const KeyEvent& rKeyEvent )
+{
+ return rKeyEvent.GetCharCode() >= 32 && rKeyEvent.GetCharCode() != 127 &&
+ KEY_MOD1 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT) && // (ssa) #i45714#:
+ KEY_MOD2 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT); // check for Ctrl and Alt separately
+}
+
+void TextEngine::ImpInitDoc()
+{
+ if ( mpDoc )
+ mpDoc->Clear();
+ else
+ mpDoc.reset( new TextDoc );
+
+ mpTEParaPortions.reset(new TEParaPortions);
+
+ std::unique_ptr<TextNode> pNode(new TextNode( OUString() ));
+ mpDoc->GetNodes().insert( mpDoc->GetNodes().begin(), std::move(pNode) );
+
+ TEParaPortion* pIniPortion = new TEParaPortion( mpDoc->GetNodes().begin()->get() );
+ mpTEParaPortions->Insert( pIniPortion, 0 );
+
+ mbFormatted = false;
+
+ ImpParagraphRemoved( TEXT_PARA_ALL );
+ ImpParagraphInserted( 0 );
+}
+
+OUString TextEngine::GetText( const TextSelection& rSel, LineEnd aSeparator ) const
+{
+ if ( !rSel.HasRange() )
+ return OUString();
+
+ TextSelection aSel( rSel );
+ aSel.Justify();
+
+ OUStringBuffer aText;
+ const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
+ const sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
+ const sal_Unicode* pSep = static_getLineEndText( aSeparator );
+ for ( sal_uInt32 nNode = aSel.GetStart().GetPara(); nNode <= nEndPara; ++nNode )
+ {
+ TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
+
+ sal_Int32 nStartPos = 0;
+ sal_Int32 nEndPos = pNode->GetText().getLength();
+ if ( nNode == nStartPara )
+ nStartPos = aSel.GetStart().GetIndex();
+ if ( nNode == nEndPara ) // may also be == nStart!
+ nEndPos = aSel.GetEnd().GetIndex();
+
+ aText.append(pNode->GetText().subView(nStartPos, nEndPos-nStartPos));
+ if ( nNode < nEndPara )
+ aText.append(pSep);
+ }
+ return aText.makeStringAndClear();
+}
+
+void TextEngine::ImpRemoveText()
+{
+ ImpInitDoc();
+
+ const TextSelection aEmptySel;
+ for (TextView* pView : *mpViews)
+ {
+ pView->ImpSetSelection( aEmptySel );
+ }
+ ResetUndo();
+}
+
+void TextEngine::SetText( const OUString& rText )
+{
+ ImpRemoveText();
+
+ const bool bUndoCurrentlyEnabled = IsUndoEnabled();
+ // the manually inserted text cannot be reversed by the user
+ EnableUndo( false );
+
+ const TextSelection aEmptySel;
+
+ TextPaM aPaM;
+ if ( !rText.isEmpty() )
+ aPaM = ImpInsertText( aEmptySel, rText );
+
+ for (TextView* pView : *mpViews)
+ {
+ pView->ImpSetSelection( aEmptySel );
+
+ // if no text, then no Format&Update => the text remains
+ if ( rText.isEmpty() && GetUpdateMode() )
+ pView->Invalidate();
+ }
+
+ if( rText.isEmpty() ) // otherwise needs invalidation later; !bFormatted is sufficient
+ mnCurTextHeight = 0;
+
+ FormatAndUpdate();
+
+ EnableUndo( bUndoCurrentlyEnabled );
+ SAL_WARN_IF( HasUndoManager() && GetUndoManager().GetUndoActionCount(), "vcl", "SetText: Undo!" );
+}
+
+void TextEngine::CursorMoved( sal_uInt32 nNode )
+{
+ // delete empty attribute; but only if paragraph is not empty!
+ TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
+ if ( pNode && pNode->GetCharAttribs().HasEmptyAttribs() && !pNode->GetText().isEmpty() )
+ pNode->GetCharAttribs().DeleteEmptyAttribs();
+}
+
+void TextEngine::ImpRemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
+{
+ SAL_WARN_IF( !nChars, "vcl", "ImpRemoveChars: 0 Chars?!" );
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ // attributes have to be saved for UNDO before RemoveChars!
+ TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ OUString aStr( pNode->GetText().copy( rPaM.GetIndex(), nChars ) );
+
+ // check if attributes are being deleted or changed
+ const sal_Int32 nStart = rPaM.GetIndex();
+ const sal_Int32 nEnd = nStart + nChars;
+ for ( sal_uInt16 nAttr = pNode->GetCharAttribs().Count(); nAttr; )
+ {
+ TextCharAttrib& rAttr = pNode->GetCharAttribs().GetAttrib( --nAttr );
+ if ( ( rAttr.GetEnd() >= nStart ) && ( rAttr.GetStart() < nEnd ) )
+ {
+ break; // for
+ }
+ }
+ InsertUndo( std::make_unique<TextUndoRemoveChars>( this, rPaM, aStr ) );
+ }
+
+ mpDoc->RemoveChars( rPaM, nChars );
+ ImpCharsRemoved( rPaM.GetPara(), rPaM.GetIndex(), nChars );
+}
+
+TextPaM TextEngine::ImpConnectParagraphs( sal_uInt32 nLeft, sal_uInt32 nRight )
+{
+ SAL_WARN_IF( nLeft == nRight, "vcl", "ImpConnectParagraphs: connect the very same paragraph ?" );
+
+ TextNode* pLeft = mpDoc->GetNodes()[ nLeft ].get();
+ TextNode* pRight = mpDoc->GetNodes()[ nRight ].get();
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoConnectParas>( this, nLeft, pLeft->GetText().getLength() ) );
+
+ // first lookup Portions, as pRight is gone after ConnectParagraphs
+ TEParaPortion* pLeftPortion = mpTEParaPortions->GetObject( nLeft );
+ TEParaPortion* pRightPortion = mpTEParaPortions->GetObject( nRight );
+ SAL_WARN_IF( !pLeft || !pLeftPortion, "vcl", "ImpConnectParagraphs(1): Hidden Portion" );
+ SAL_WARN_IF( !pRight || !pRightPortion, "vcl", "ImpConnectParagraphs(2): Hidden Portion" );
+
+ TextPaM aPaM = mpDoc->ConnectParagraphs( pLeft, pRight );
+ ImpParagraphRemoved( nRight );
+
+ pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() );
+
+ mpTEParaPortions->Remove( nRight );
+ // the right Node is deleted by EditDoc::ConnectParagraphs()
+
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpDeleteText( const TextSelection& rSel )
+{
+ if ( !rSel.HasRange() )
+ return rSel.GetStart();
+
+ TextSelection aSel( rSel );
+ aSel.Justify();
+ TextPaM aStartPaM( aSel.GetStart() );
+ TextPaM aEndPaM( aSel.GetEnd() );
+
+ CursorMoved( aStartPaM.GetPara() ); // so that newly-adjusted attributes vanish
+ CursorMoved( aEndPaM.GetPara() ); // so that newly-adjusted attributes vanish
+
+ SAL_WARN_IF( !mpDoc->IsValidPaM( aStartPaM ), "vcl", "ImpDeleteText(1): bad Index" );
+ SAL_WARN_IF( !mpDoc->IsValidPaM( aEndPaM ), "vcl", "ImpDeleteText(2): bad Index" );
+
+ const sal_uInt32 nStartNode = aStartPaM.GetPara();
+ sal_uInt32 nEndNode = aEndPaM.GetPara();
+
+ // remove all Nodes inbetween
+ for ( sal_uInt32 z = nStartNode+1; z < nEndNode; ++z )
+ {
+ // always nStartNode+1, because of Remove()!
+ ImpRemoveParagraph( nStartNode+1 );
+ }
+
+ if ( nStartNode != nEndNode )
+ {
+ // the remainder of StartNodes...
+ TextNode* pLeft = mpDoc->GetNodes()[ nStartNode ].get();
+ sal_Int32 nChars = pLeft->GetText().getLength() - aStartPaM.GetIndex();
+ if ( nChars )
+ {
+ ImpRemoveChars( aStartPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(3): bad Index" );
+ pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() );
+ }
+
+ // the beginning of EndNodes...
+ nEndNode = nStartNode+1; // the other paragraphs were deleted
+ nChars = aEndPaM.GetIndex();
+ if ( nChars )
+ {
+ aEndPaM.GetPara() = nEndNode;
+ aEndPaM.GetIndex() = 0;
+ ImpRemoveChars( aEndPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nEndNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(4): bad Index" );
+ pPortion->MarkSelectionInvalid( 0 );
+ }
+
+ // connect...
+ aStartPaM = ImpConnectParagraphs( nStartNode, nEndNode );
+ }
+ else
+ {
+ const sal_Int32 nChars = aEndPaM.GetIndex() - aStartPaM.GetIndex();
+ ImpRemoveChars( aStartPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(5): bad Index" );
+ pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() );
+ }
+
+// UpdateSelections();
+ TextModified();
+ return aStartPaM;
+}
+
+void TextEngine::ImpRemoveParagraph( sal_uInt32 nPara )
+{
+ std::unique_ptr<TextNode> pNode = std::move(mpDoc->GetNodes()[ nPara ]);
+
+ // the Node is handled by Undo and is deleted if appropriate
+ mpDoc->GetNodes().erase( mpDoc->GetNodes().begin() + nPara );
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoDelPara>( this, pNode.release(), nPara ) );
+
+ mpTEParaPortions->Remove( nPara );
+
+ ImpParagraphRemoved( nPara );
+}
+
+uno::Reference < i18n::XExtendedInputSequenceChecker > const & TextEngine::GetInputSequenceChecker()
+{
+ if ( !mxISC.is() )
+ {
+ mxISC = i18n::InputSequenceChecker::create( ::comphelper::getProcessComponentContext() );
+ }
+ return mxISC;
+}
+
+bool TextEngine::IsInputSequenceCheckingRequired( sal_Unicode c, const TextSelection& rCurSel ) const
+{
+ SvtCTLOptions aCTLOptions;
+
+ // get the index that really is first
+ const sal_Int32 nFirstPos = std::min(rCurSel.GetStart().GetIndex(), rCurSel.GetEnd().GetIndex());
+
+ bool bIsSequenceChecking =
+ aCTLOptions.IsCTLFontEnabled() &&
+ aCTLOptions.IsCTLSequenceChecking() &&
+ nFirstPos != 0; /* first char needs not to be checked */
+
+ if (bIsSequenceChecking)
+ {
+ uno::Reference< i18n::XBreakIterator > xBI = const_cast<TextEngine *>(this)->GetBreakIterator();
+ bIsSequenceChecking = xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( OUString( c ), 0 );
+ }
+
+ return bIsSequenceChecking;
+}
+
+TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, sal_Unicode c, bool bOverwrite )
+{
+ return ImpInsertText( c, rCurSel, bOverwrite );
+}
+
+TextPaM TextEngine::ImpInsertText( sal_Unicode c, const TextSelection& rCurSel, bool bOverwrite, bool bIsUserInput )
+{
+ SAL_WARN_IF( c == '\n', "vcl", "InsertText: NewLine!" );
+ SAL_WARN_IF( c == '\r', "vcl", "InsertText: NewLine!" );
+
+ TextPaM aPaM( rCurSel.GetStart() );
+ TextNode* pNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+
+ bool bDoOverwrite = bOverwrite && ( aPaM.GetIndex() < pNode->GetText().getLength() );
+
+ bool bUndoAction = rCurSel.HasRange() || bDoOverwrite;
+
+ if ( bUndoAction )
+ UndoActionStart();
+
+ if ( rCurSel.HasRange() )
+ {
+ aPaM = ImpDeleteText( rCurSel );
+ }
+ else if ( bDoOverwrite )
+ {
+ // if selection, then don't overwrite a character
+ TextSelection aTmpSel( aPaM );
+ ++aTmpSel.GetEnd().GetIndex();
+ ImpDeleteText( aTmpSel );
+ }
+
+ if (bIsUserInput && IsInputSequenceCheckingRequired( c, rCurSel ))
+ {
+ uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = GetInputSequenceChecker();
+ SvtCTLOptions aCTLOptions;
+
+ if (xISC.is())
+ {
+ sal_Int32 nTmpPos = aPaM.GetIndex();
+ sal_Int16 nCheckMode = aCTLOptions.IsCTLSequenceCheckingRestricted() ?
+ i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
+
+ // the text that needs to be checked is only the one
+ // before the current cursor position
+ OUString aOldText( mpDoc->GetText( aPaM.GetPara() ).copy(0, nTmpPos) );
+ if (aCTLOptions.IsCTLSequenceCheckingTypeAndReplace())
+ {
+ OUString aNewText( aOldText );
+ xISC->correctInputSequence( aNewText, nTmpPos - 1, c, nCheckMode );
+
+ // find position of first character that has changed
+ const sal_Int32 nOldLen = aOldText.getLength();
+ const sal_Int32 nNewLen = aNewText.getLength();
+ const sal_Unicode *pOldTxt = aOldText.getStr();
+ const sal_Unicode *pNewTxt = aNewText.getStr();
+ sal_Int32 nChgPos = 0;
+ while ( nChgPos < nOldLen && nChgPos < nNewLen &&
+ pOldTxt[nChgPos] == pNewTxt[nChgPos] )
+ ++nChgPos;
+
+ OUString aChgText( aNewText.copy( nChgPos ) );
+
+ // select text from first pos to be changed to current pos
+ TextSelection aSel( TextPaM( aPaM.GetPara(), nChgPos ), aPaM );
+
+ if (!aChgText.isEmpty())
+ // ImpInsertText implicitly handles undo...
+ return ImpInsertText( aSel, aChgText );
+ else
+ return aPaM;
+ }
+ else
+ {
+ // should the character be ignored (i.e. not get inserted) ?
+ if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode ))
+ return aPaM; // nothing to be done -> no need for undo
+ }
+ }
+
+ // at this point now we will insert the character 'normally' some lines below...
+ }
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ std::unique_ptr<TextUndoInsertChars> pNewUndo(new TextUndoInsertChars( this, aPaM, OUString(c) ));
+ bool bTryMerge = !bDoOverwrite && ( c != ' ' );
+ InsertUndo( std::move(pNewUndo), bTryMerge );
+ }
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
+ pPortion->MarkInvalid( aPaM.GetIndex(), 1 );
+ if ( c == '\t' )
+ pPortion->SetNotSimpleInvalid();
+ aPaM = mpDoc->InsertText( aPaM, c );
+ ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-1, 1 );
+
+ TextModified();
+
+ if ( bUndoAction )
+ UndoActionEnd();
+
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, const OUString& rStr )
+{
+ UndoActionStart();
+
+ TextPaM aPaM;
+
+ if ( rCurSel.HasRange() )
+ aPaM = ImpDeleteText( rCurSel );
+ else
+ aPaM = rCurSel.GetEnd();
+
+ OUString aText(convertLineEnd(rStr, LINEEND_LF));
+
+ sal_Int32 nStart = 0;
+ while ( nStart < aText.getLength() )
+ {
+ sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart );
+ if (nEnd == -1)
+ nEnd = aText.getLength(); // do not dereference!
+
+ // Start == End => empty line
+ if ( nEnd > nStart )
+ {
+ OUString aLine(aText.copy(nStart, nEnd-nStart));
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoInsertChars>( this, aPaM, aLine ) );
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
+ pPortion->MarkInvalid( aPaM.GetIndex(), aLine.getLength() );
+ if (aLine.indexOf( '\t' ) != -1)
+ pPortion->SetNotSimpleInvalid();
+
+ aPaM = mpDoc->InsertText( aPaM, aLine );
+ ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-aLine.getLength(), aLine.getLength() );
+
+ }
+ if ( nEnd < aText.getLength() )
+ aPaM = ImpInsertParaBreak( aPaM );
+
+ if ( nEnd == aText.getLength() ) // #108611# prevent overflow in "nStart = nEnd+1" calculation
+ break;
+
+ nStart = nEnd+1;
+ }
+
+ UndoActionEnd();
+
+ TextModified();
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpInsertParaBreak( const TextSelection& rCurSel )
+{
+ TextPaM aPaM;
+ if ( rCurSel.HasRange() )
+ aPaM = ImpDeleteText( rCurSel );
+ else
+ aPaM = rCurSel.GetEnd();
+
+ return ImpInsertParaBreak( aPaM );
+}
+
+TextPaM TextEngine::ImpInsertParaBreak( const TextPaM& rPaM )
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoSplitPara>( this, rPaM.GetPara(), rPaM.GetIndex() ) );
+
+ TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ bool bFirstParaContentChanged = rPaM.GetIndex() < pNode->GetText().getLength();
+
+ TextPaM aPaM( mpDoc->InsertParaBreak( rPaM ) );
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpInsertParaBreak: Hidden Portion" );
+ pPortion->MarkInvalid( rPaM.GetIndex(), 0 );
+
+ TextNode* pNewNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ TEParaPortion* pNewPortion = new TEParaPortion( pNewNode );
+ mpTEParaPortions->Insert( pNewPortion, aPaM.GetPara() );
+ ImpParagraphInserted( aPaM.GetPara() );
+
+ CursorMoved( rPaM.GetPara() ); // if empty attribute created
+ TextModified();
+
+ if ( bFirstParaContentChanged )
+ Broadcast( TextHint( SfxHintId::TextParaContentChanged, rPaM.GetPara() ) );
+
+ return aPaM;
+}
+
+tools::Rectangle TextEngine::PaMtoEditCursor( const TextPaM& rPaM, bool bSpecial )
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "PaMtoEditCursor: GetUpdateMode()" );
+
+ tools::Rectangle aEditCursor;
+ tools::Long nY = 0;
+
+ if ( !mbHasMultiLineParas )
+ {
+ nY = rPaM.GetPara() * mnCharHeight;
+ }
+ else
+ {
+ for ( sal_uInt32 nPortion = 0; nPortion < rPaM.GetPara(); ++nPortion )
+ {
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject(nPortion);
+ nY += pPortion->GetLines().size() * mnCharHeight;
+ }
+ }
+
+ aEditCursor = GetEditCursor( rPaM, bSpecial );
+ aEditCursor.AdjustTop(nY );
+ aEditCursor.AdjustBottom(nY );
+ return aEditCursor;
+}
+
+tools::Rectangle TextEngine::GetEditCursor( const TextPaM& rPaM, bool bSpecial, bool bPreferPortionStart )
+{
+ if ( !IsFormatted() && !IsFormatting() )
+ FormatAndUpdate();
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
+ //TextNode* pNode = mpDoc->GetNodes().GetObject( rPaM.GetPara() );
+
+ /*
+ bSpecial: If behind the last character of a made up line, stay at the
+ end of the line, not at the start of the next line.
+ Purpose: - really END = > behind the last character
+ - to selection...
+
+ */
+
+ tools::Long nY = 0;
+ sal_Int32 nCurIndex = 0;
+ TextLine* pLine = nullptr;
+ for (TextLine & rTmpLine : pPortion->GetLines())
+ {
+ if ( ( rTmpLine.GetStart() == rPaM.GetIndex() ) || ( rTmpLine.IsIn( rPaM.GetIndex(), bSpecial ) ) )
+ {
+ pLine = &rTmpLine;
+ break;
+ }
+
+ nCurIndex = nCurIndex + rTmpLine.GetLen();
+ nY += mnCharHeight;
+ }
+ if ( !pLine )
+ {
+ // Cursor at end of paragraph
+ SAL_WARN_IF( rPaM.GetIndex() != nCurIndex, "vcl", "GetEditCursor: Bad Index!" );
+
+ pLine = & ( pPortion->GetLines().back() );
+ nY -= mnCharHeight;
+ }
+
+ tools::Rectangle aEditCursor;
+
+ aEditCursor.SetTop( nY );
+ nY += mnCharHeight;
+ aEditCursor.SetBottom( nY-1 );
+
+ // search within the line
+ tools::Long nX = ImpGetXPos( rPaM.GetPara(), pLine, rPaM.GetIndex(), bPreferPortionStart );
+ aEditCursor.SetLeft(nX);
+ aEditCursor.SetRight(nX);
+ return aEditCursor;
+}
+
+tools::Long TextEngine::ImpGetXPos( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart )
+{
+ SAL_WARN_IF( ( nIndex < pLine->GetStart() ) || ( nIndex > pLine->GetEnd() ) , "vcl", "ImpGetXPos: Bad parameters!" );
+
+ bool bDoPreferPortionStart = bPreferPortionStart;
+ // Assure that the portion belongs to this line
+ if ( nIndex == pLine->GetStart() )
+ bDoPreferPortionStart = true;
+ else if ( nIndex == pLine->GetEnd() )
+ bDoPreferPortionStart = false;
+
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ sal_Int32 nTextPortionStart = 0;
+ std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart );
+
+ SAL_WARN_IF( ( nTextPortion < pLine->GetStartPortion() ) || ( nTextPortion > pLine->GetEndPortion() ), "vcl", "GetXPos: Portion not in current line!" );
+
+ TETextPortion& rPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
+
+ tools::Long nX = ImpGetPortionXOffset( nPara, pLine, nTextPortion );
+
+ tools::Long nPortionTextWidth = rPortion.GetWidth();
+
+ if ( nTextPortionStart != nIndex )
+ {
+ // Search within portion...
+ if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) )
+ {
+ // End of Portion
+ if ( ( rPortion.GetKind() == PORTIONKIND_TAB ) ||
+ ( !IsRightToLeft() && !rPortion.IsRightToLeft() ) ||
+ ( IsRightToLeft() && rPortion.IsRightToLeft() ) )
+ {
+ nX += nPortionTextWidth;
+ if ( ( rPortion.GetKind() == PORTIONKIND_TAB ) && ( (nTextPortion+1) < pParaPortion->GetTextPortions().size() ) )
+ {
+ TETextPortion& rNextPortion = pParaPortion->GetTextPortions()[ nTextPortion+1 ];
+ if (rNextPortion.GetKind() != PORTIONKIND_TAB && IsRightToLeft() != rNextPortion.IsRightToLeft())
+ {
+ // End of the tab portion, use start of next for cursor pos
+ SAL_WARN_IF( bPreferPortionStart, "vcl", "ImpGetXPos: How can we get here!" );
+ nX = ImpGetXPos( nPara, pLine, nIndex, true );
+ }
+
+ }
+ }
+ }
+ else if ( rPortion.GetKind() == PORTIONKIND_TEXT )
+ {
+ SAL_WARN_IF( nIndex == pLine->GetStart(), "vcl", "ImpGetXPos: Strange behavior" );
+
+ tools::Long nPosInPortion = CalcTextWidth( nPara, nTextPortionStart, nIndex-nTextPortionStart );
+
+ if (IsRightToLeft() == rPortion.IsRightToLeft())
+ {
+ nX += nPosInPortion;
+ }
+ else
+ {
+ nX += nPortionTextWidth - nPosInPortion;
+ }
+ }
+ }
+ else // if ( nIndex == pLine->GetStart() )
+ {
+ if (rPortion.GetKind() != PORTIONKIND_TAB && IsRightToLeft() != rPortion.IsRightToLeft())
+ {
+ nX += nPortionTextWidth;
+ }
+ }
+
+ return nX;
+}
+
+const TextAttrib* TextEngine::FindAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
+{
+ const TextAttrib* pAttr = nullptr;
+ const TextCharAttrib* pCharAttr = FindCharAttrib( rPaM, nWhich );
+ if ( pCharAttr )
+ pAttr = &pCharAttr->GetAttr();
+ return pAttr;
+}
+
+const TextCharAttrib* TextEngine::FindCharAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
+{
+ const TextCharAttrib* pAttr = nullptr;
+ TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ if (pNode && (rPaM.GetIndex() <= pNode->GetText().getLength()))
+ pAttr = pNode->GetCharAttribs().FindAttrib( nWhich, rPaM.GetIndex() );
+ return pAttr;
+}
+
+TextPaM TextEngine::GetPaM( const Point& rDocPos )
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetPaM: GetUpdateMode()" );
+
+ tools::Long nY = 0;
+ for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
+ {
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
+ tools::Long nTmpHeight = pPortion->GetLines().size() * mnCharHeight;
+ nY += nTmpHeight;
+ if ( nY > rDocPos.Y() )
+ {
+ nY -= nTmpHeight;
+ Point aPosInPara( rDocPos );
+ aPosInPara.AdjustY( -nY );
+
+ TextPaM aPaM( nPortion, 0 );
+ aPaM.GetIndex() = ImpFindIndex( nPortion, aPosInPara );
+ return aPaM;
+ }
+ }
+
+ // not found - go to last visible
+ const sal_uInt32 nLastNode = static_cast<sal_uInt32>(mpDoc->GetNodes().size() - 1);
+ TextNode* pLast = mpDoc->GetNodes()[ nLastNode ].get();
+ return TextPaM( nLastNode, pLast->GetText().getLength() );
+}
+
+sal_Int32 TextEngine::ImpFindIndex( sal_uInt32 nPortion, const Point& rPosInPara )
+{
+ SAL_WARN_IF( !IsFormatted(), "vcl", "GetPaM: Not formatted" );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
+
+ sal_Int32 nCurIndex = 0;
+
+ tools::Long nY = 0;
+ TextLine* pLine = nullptr;
+ std::vector<TextLine>::size_type nLine;
+ for ( nLine = 0; nLine < pPortion->GetLines().size(); nLine++ )
+ {
+ TextLine& rmpLine = pPortion->GetLines()[ nLine ];
+ nY += mnCharHeight;
+ if ( nY > rPosInPara.Y() ) // that's it
+ {
+ pLine = &rmpLine;
+ break; // correct Y-Position not needed
+ }
+ }
+
+ assert(pLine && "ImpFindIndex: pLine ?");
+
+ nCurIndex = GetCharPos( nPortion, nLine, rPosInPara.X() );
+
+ if ( nCurIndex && ( nCurIndex == pLine->GetEnd() ) &&
+ ( pLine != &( pPortion->GetLines().back() ) ) )
+ {
+ uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
+ sal_Int32 nCount = 1;
+ nCurIndex = xBI->previousCharacters( pPortion->GetNode()->GetText(), nCurIndex, GetLocale(), i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
+ }
+ return nCurIndex;
+}
+
+sal_Int32 TextEngine::GetCharPos( sal_uInt32 nPortion, std::vector<TextLine>::size_type nLine, tools::Long nXPos )
+{
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
+ TextLine& rLine = pPortion->GetLines()[ nLine ];
+
+ sal_Int32 nCurIndex = rLine.GetStart();
+
+ tools::Long nTmpX = rLine.GetStartX();
+ if ( nXPos <= nTmpX )
+ return nCurIndex;
+
+ for ( std::size_t i = rLine.GetStartPortion(); i <= rLine.GetEndPortion(); i++ )
+ {
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ i ];
+ nTmpX += rTextPortion.GetWidth();
+
+ if ( nTmpX > nXPos )
+ {
+ if( rTextPortion.GetLen() > 1 )
+ {
+ nTmpX -= rTextPortion.GetWidth(); // position before Portion
+ // TODO: Optimize: no GetTextBreak if fixed-width Font
+ vcl::Font aFont;
+ SeekCursor( nPortion, nCurIndex+1, aFont, nullptr );
+ mpRefDev->SetFont( aFont);
+ tools::Long nPosInPortion = nXPos-nTmpX;
+ if ( IsRightToLeft() != rTextPortion.IsRightToLeft() )
+ nPosInPortion = rTextPortion.GetWidth() - nPosInPortion;
+ nCurIndex = mpRefDev->GetTextBreak( pPortion->GetNode()->GetText(), nPosInPortion, nCurIndex );
+ // MT: GetTextBreak should assure that we are not within a CTL cell...
+ }
+ return nCurIndex;
+ }
+ nCurIndex += rTextPortion.GetLen();
+ }
+ return nCurIndex;
+}
+
+tools::Long TextEngine::GetTextHeight() const
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
+
+ if ( !IsFormatted() && !IsFormatting() )
+ const_cast<TextEngine*>(this)->FormatAndUpdate();
+
+ return mnCurTextHeight;
+}
+
+tools::Long TextEngine::GetTextHeight( sal_uInt32 nParagraph ) const
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
+
+ if ( !IsFormatted() && !IsFormatting() )
+ const_cast<TextEngine*>(this)->FormatAndUpdate();
+
+ return CalcParaHeight( nParagraph );
+}
+
+tools::Long TextEngine::CalcTextWidth( sal_uInt32 nPara )
+{
+ tools::Long nParaWidth = 0;
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
+ for ( auto nLine = pPortion->GetLines().size(); nLine; )
+ {
+ tools::Long nLineWidth = 0;
+ TextLine& rLine = pPortion->GetLines()[ --nLine ];
+ for ( std::size_t nTP = rLine.GetStartPortion(); nTP <= rLine.GetEndPortion(); nTP++ )
+ {
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ nTP ];
+ nLineWidth += rTextPortion.GetWidth();
+ }
+ if ( nLineWidth > nParaWidth )
+ nParaWidth = nLineWidth;
+ }
+ return nParaWidth;
+}
+
+tools::Long TextEngine::CalcTextWidth()
+{
+ if ( !IsFormatted() && !IsFormatting() )
+ FormatAndUpdate();
+
+ if ( mnCurTextWidth < 0 )
+ {
+ mnCurTextWidth = 0;
+ for ( sal_uInt32 nPara = mpTEParaPortions->Count(); nPara; )
+ {
+ const tools::Long nParaWidth = CalcTextWidth( --nPara );
+ if ( nParaWidth > mnCurTextWidth )
+ mnCurTextWidth = nParaWidth;
+ }
+ }
+ return mnCurTextWidth+1;// wider by 1, as CreateLines breaks at >=
+}
+
+tools::Long TextEngine::CalcTextHeight() const
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "CalcTextHeight: GetUpdateMode()" );
+
+ tools::Long nY = 0;
+ for ( auto nPortion = mpTEParaPortions->Count(); nPortion; )
+ nY += CalcParaHeight( --nPortion );
+ return nY;
+}
+
+tools::Long TextEngine::CalcTextWidth( sal_uInt32 nPara, sal_Int32 nPortionStart, sal_Int32 nLen )
+{
+#ifdef DBG_UTIL
+ // within the text there must not be a Portion change (attribute/tab)!
+ sal_Int32 nTabPos = mpDoc->GetNodes()[ nPara ]->GetText().indexOf( '\t', nPortionStart );
+ SAL_WARN_IF( nTabPos != -1 && nTabPos < (nPortionStart+nLen), "vcl", "CalcTextWidth: Tab!" );
+#endif
+
+ vcl::Font aFont;
+ SeekCursor( nPara, nPortionStart+1, aFont, nullptr );
+ mpRefDev->SetFont( aFont );
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ tools::Long nWidth = mpRefDev->GetTextWidth( pNode->GetText(), nPortionStart, nLen );
+ return nWidth;
+}
+
+void TextEngine::GetTextPortionRange(const TextPaM& rPaM, sal_Int32& nStart, sal_Int32& nEnd)
+{
+ nStart = 0;
+ nEnd = 0;
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
+ for ( std::size_t i = 0; i < pParaPortion->GetTextPortions().size(); ++i )
+ {
+ TETextPortion& rTextPortion = pParaPortion->GetTextPortions()[ i ];
+ if (nStart + rTextPortion.GetLen() > rPaM.GetIndex())
+ {
+ nEnd = nStart + rTextPortion.GetLen();
+ return;
+ }
+ else
+ {
+ nStart += rTextPortion.GetLen();
+ }
+ }
+}
+
+sal_uInt16 TextEngine::GetLineCount( sal_uInt32 nParagraph ) const
+{
+ SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
+
+ TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
+ if ( pPPortion )
+ return pPPortion->GetLines().size();
+
+ return 0;
+}
+
+sal_Int32 TextEngine::GetLineLen( sal_uInt32 nParagraph, sal_uInt16 nLine ) const
+{
+ SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
+
+ TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
+ if ( pPPortion && ( nLine < pPPortion->GetLines().size() ) )
+ {
+ return pPPortion->GetLines()[ nLine ].GetLen();
+ }
+
+ return 0;
+}
+
+tools::Long TextEngine::CalcParaHeight( sal_uInt32 nParagraph ) const
+{
+ tools::Long nHeight = 0;
+
+ TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
+ SAL_WARN_IF( !pPPortion, "vcl", "GetParaHeight: paragraph not found" );
+ if ( pPPortion )
+ nHeight = pPPortion->GetLines().size() * mnCharHeight;
+
+ return nHeight;
+}
+
+Range TextEngine::GetInvalidYOffsets( sal_uInt32 nPortion )
+{
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
+ sal_uInt16 nLines = pTEParaPortion->GetLines().size();
+ sal_uInt16 nLastInvalid, nFirstInvalid = 0;
+ sal_uInt16 nLine;
+ for ( nLine = 0; nLine < nLines; nLine++ )
+ {
+ TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
+ if ( rL.IsInvalid() )
+ {
+ nFirstInvalid = nLine;
+ break;
+ }
+ }
+
+ for ( nLastInvalid = nFirstInvalid; nLastInvalid < nLines; nLastInvalid++ )
+ {
+ TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
+ if ( rL.IsValid() )
+ break;
+ }
+
+ if ( nLastInvalid >= nLines )
+ nLastInvalid = nLines-1;
+
+ return Range( nFirstInvalid*mnCharHeight, ((nLastInvalid+1)*mnCharHeight)-1 );
+}
+
+sal_uInt32 TextEngine::GetParagraphCount() const
+{
+ return static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+}
+
+void TextEngine::EnableUndo( bool bEnable )
+{
+ // delete list when switching mode
+ if ( bEnable != IsUndoEnabled() )
+ ResetUndo();
+
+ mbUndoEnabled = bEnable;
+}
+
+SfxUndoManager& TextEngine::GetUndoManager()
+{
+ if ( !mpUndoManager )
+ mpUndoManager.reset( new TextUndoManager( this ) );
+ return *mpUndoManager;
+}
+
+void TextEngine::UndoActionStart( sal_uInt16 nId )
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ GetUndoManager().EnterListAction( OUString(), OUString(), nId, ViewShellId(-1) );
+ }
+}
+
+void TextEngine::UndoActionEnd()
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ GetUndoManager().LeaveListAction();
+}
+
+void TextEngine::InsertUndo( std::unique_ptr<TextUndo> pUndo, bool bTryMerge )
+{
+ SAL_WARN_IF( IsInUndo(), "vcl", "InsertUndo: in Undo mode!" );
+ GetUndoManager().AddUndoAction( std::move(pUndo), bTryMerge );
+}
+
+void TextEngine::ResetUndo()
+{
+ if ( mpUndoManager )
+ mpUndoManager->Clear();
+}
+
+void TextEngine::InsertContent( std::unique_ptr<TextNode> pNode, sal_uInt32 nPara )
+{
+ SAL_WARN_IF( !pNode, "vcl", "InsertContent: NULL-Pointer!" );
+ SAL_WARN_IF( !IsInUndo(), "vcl", "InsertContent: only in Undo()!" );
+ TEParaPortion* pNew = new TEParaPortion( pNode.get() );
+ mpTEParaPortions->Insert( pNew, nPara );
+ mpDoc->GetNodes().insert( mpDoc->GetNodes().begin() + nPara, std::move(pNode) );
+ ImpParagraphInserted( nPara );
+}
+
+TextPaM TextEngine::SplitContent( sal_uInt32 nNode, sal_Int32 nSepPos )
+{
+#ifdef DBG_UTIL
+ TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
+ SAL_WARN_IF( !pNode, "vcl", "SplitContent: Invalid Node!" );
+ SAL_WARN_IF( !IsInUndo(), "vcl", "SplitContent: only in Undo()!" );
+ SAL_WARN_IF( nSepPos > pNode->GetText().getLength(), "vcl", "SplitContent: Bad index" );
+#endif
+ TextPaM aPaM( nNode, nSepPos );
+ return ImpInsertParaBreak( aPaM );
+}
+
+TextPaM TextEngine::ConnectContents( sal_uInt32 nLeftNode )
+{
+ SAL_WARN_IF( !IsInUndo(), "vcl", "ConnectContent: only in Undo()!" );
+ return ImpConnectParagraphs( nLeftNode, nLeftNode+1 );
+}
+
+void TextEngine::SeekCursor( sal_uInt32 nPara, sal_Int32 nPos, vcl::Font& rFont, OutputDevice* pOutDev )
+{
+ rFont = maFont;
+ if ( pOutDev )
+ pOutDev->SetTextColor( maTextColor );
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
+ if ( rAttrib.GetStart() > nPos )
+ break;
+
+ // When seeking don't use Attr that start there!
+ // Do not use empty attributes:
+ // - If just being setup and empty => no effect on Font
+ // - Characters that are setup in an empty paragraph become visible right away.
+ if ( ( ( rAttrib.GetStart() < nPos ) && ( rAttrib.GetEnd() >= nPos ) )
+ || pNode->GetText().isEmpty() )
+ {
+ if ( rAttrib.Which() != TEXTATTR_FONTCOLOR )
+ {
+ rAttrib.GetAttr().SetFont(rFont);
+ }
+ else
+ {
+ if ( pOutDev )
+ pOutDev->SetTextColor( static_cast<const TextAttribFontColor&>(rAttrib.GetAttr()).GetColor() );
+ }
+ }
+ }
+
+ if ( !(mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) &&
+ ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) )) )
+ return;
+
+ ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
+ if ( nAttr & ExtTextInputAttr::Underline )
+ rFont.SetUnderline( LINESTYLE_SINGLE );
+ else if ( nAttr & ExtTextInputAttr::DoubleUnderline )
+ rFont.SetUnderline( LINESTYLE_DOUBLE );
+ else if ( nAttr & ExtTextInputAttr::BoldUnderline )
+ rFont.SetUnderline( LINESTYLE_BOLD );
+ else if ( nAttr & ExtTextInputAttr::DottedUnderline )
+ rFont.SetUnderline( LINESTYLE_DOTTED );
+ else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
+ rFont.SetUnderline( LINESTYLE_DOTTED );
+ if ( nAttr & ExtTextInputAttr::RedText )
+ rFont.SetColor( COL_RED );
+ else if ( nAttr & ExtTextInputAttr::HalfToneText )
+ rFont.SetColor( COL_LIGHTGRAY );
+ if ( nAttr & ExtTextInputAttr::Highlight )
+ {
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ rFont.SetColor( rStyleSettings.GetHighlightTextColor() );
+ rFont.SetFillColor( rStyleSettings.GetHighlightColor() );
+ rFont.SetTransparent( false );
+ }
+ else if ( nAttr & ExtTextInputAttr::GrayWaveline )
+ {
+ rFont.SetUnderline( LINESTYLE_WAVE );
+// if( pOut )
+// pOut->SetTextLineColor( COL_LIGHTGRAY );
+ }
+}
+
+void TextEngine::FormatAndUpdate( TextView* pCurView )
+{
+ if ( mbDowning )
+ return;
+
+ if ( IsInUndo() )
+ IdleFormatAndUpdate( pCurView );
+ else
+ {
+ FormatDoc();
+ UpdateViews( pCurView );
+ }
+}
+
+void TextEngine::IdleFormatAndUpdate( TextView* pCurView, sal_uInt16 nMaxTimerRestarts )
+{
+ mpIdleFormatter->DoIdleFormat( pCurView, nMaxTimerRestarts );
+}
+
+void TextEngine::TextModified()
+{
+ mbFormatted = false;
+ mbModified = true;
+}
+
+void TextEngine::UpdateViews( TextView* pCurView )
+{
+ if ( !GetUpdateMode() || IsFormatting() || maInvalidRect.IsEmpty() )
+ return;
+
+ SAL_WARN_IF( !IsFormatted(), "vcl", "UpdateViews: Doc not formatted!" );
+
+ for (TextView* pView : *mpViews)
+ {
+ pView->HideCursor();
+
+ tools::Rectangle aClipRect( maInvalidRect );
+ const Size aOutSz = pView->GetWindow()->GetOutputSizePixel();
+ const tools::Rectangle aVisArea( pView->GetStartDocPos(), aOutSz );
+ aClipRect.Intersection( aVisArea );
+ if ( !aClipRect.IsEmpty() )
+ {
+ // translate into window coordinates
+ Point aNewPos = pView->GetWindowPos( aClipRect.TopLeft() );
+ if ( IsRightToLeft() )
+ aNewPos.AdjustX( -(aOutSz.Width() - 1) );
+ aClipRect.SetPos( aNewPos );
+
+ pView->GetWindow()->Invalidate( aClipRect );
+ }
+ }
+
+ if ( pCurView )
+ {
+ pCurView->ShowCursor( pCurView->IsAutoScroll() );
+ }
+
+ maInvalidRect = tools::Rectangle();
+}
+
+IMPL_LINK_NOARG(TextEngine, IdleFormatHdl, Timer *, void)
+{
+ FormatAndUpdate( mpIdleFormatter->GetView() );
+}
+
+void TextEngine::CheckIdleFormatter()
+{
+ mpIdleFormatter->ForceTimeout();
+}
+
+void TextEngine::FormatFullDoc()
+{
+ for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
+ {
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
+ pTEParaPortion->MarkSelectionInvalid( 0 );
+ }
+ mbFormatted = false;
+ FormatDoc();
+}
+
+void TextEngine::FormatDoc()
+{
+ if ( IsFormatted() || !GetUpdateMode() || IsFormatting() )
+ return;
+
+ mbIsFormatting = true;
+ mbHasMultiLineParas = false;
+
+ tools::Long nY = 0;
+ bool bGrow = false;
+
+ maInvalidRect = tools::Rectangle(); // clear
+ for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
+ {
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ if ( pTEParaPortion->IsInvalid() )
+ {
+ const tools::Long nOldParaWidth = mnCurTextWidth >= 0 ? CalcTextWidth( nPara ) : -1;
+
+ Broadcast( TextHint( SfxHintId::TextFormatPara, nPara ) );
+
+ if ( CreateLines( nPara ) )
+ bGrow = true;
+
+ // set InvalidRect only once
+ if ( maInvalidRect.IsEmpty() )
+ {
+ // otherwise remains Empty() for Paperwidth 0 (AutoPageSize)
+ const tools::Long nWidth = mnMaxTextWidth
+ ? mnMaxTextWidth
+ : std::numeric_limits<tools::Long>::max();
+ const Range aInvRange( GetInvalidYOffsets( nPara ) );
+ maInvalidRect = tools::Rectangle( Point( 0, nY+aInvRange.Min() ),
+ Size( nWidth, aInvRange.Len() ) );
+ }
+ else
+ {
+ maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
+ }
+
+ if ( mnCurTextWidth >= 0 )
+ {
+ const tools::Long nNewParaWidth = CalcTextWidth( nPara );
+ if ( nNewParaWidth >= mnCurTextWidth )
+ mnCurTextWidth = nNewParaWidth;
+ else if ( nOldParaWidth >= mnCurTextWidth )
+ mnCurTextWidth = -1;
+ }
+ }
+ else if ( bGrow )
+ {
+ maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
+ }
+ nY += CalcParaHeight( nPara );
+ if ( !mbHasMultiLineParas && pTEParaPortion->GetLines().size() > 1 )
+ mbHasMultiLineParas = true;
+ }
+
+ if ( !maInvalidRect.IsEmpty() )
+ {
+ const tools::Long nNewHeight = CalcTextHeight();
+ const tools::Long nDiff = nNewHeight - mnCurTextHeight;
+ if ( nNewHeight < mnCurTextHeight )
+ {
+ maInvalidRect.SetBottom( std::max( nNewHeight, mnCurTextHeight ) );
+ if ( maInvalidRect.IsEmpty() )
+ {
+ maInvalidRect.SetTop( 0 );
+ // Left and Right are not evaluated, but set because of IsEmpty
+ maInvalidRect.SetLeft( 0 );
+ maInvalidRect.SetRight( mnMaxTextWidth );
+ }
+ }
+
+ mnCurTextHeight = nNewHeight;
+ if ( nDiff )
+ {
+ mbFormatted = true;
+ Broadcast( TextHint( SfxHintId::TextHeightChanged ) );
+ }
+ }
+
+ mbIsFormatting = false;
+ mbFormatted = true;
+
+ Broadcast( TextHint( SfxHintId::TextFormatted ) );
+}
+
+void TextEngine::CreateAndInsertEmptyLine( sal_uInt32 nPara )
+{
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ TextLine aTmpLine;
+ aTmpLine.SetStart( pNode->GetText().getLength() );
+ aTmpLine.SetEnd( aTmpLine.GetStart() );
+
+ if ( ImpGetAlign() == TxtAlign::Center )
+ aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth / 2) );
+ else if ( ImpGetAlign() == TxtAlign::Right )
+ aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth) );
+ else
+ aTmpLine.SetStartX( mpDoc->GetLeftMargin() );
+
+ bool bLineBreak = !pNode->GetText().isEmpty();
+
+ TETextPortion aDummyPortion( 0 );
+ aDummyPortion.GetWidth() = 0;
+ pTEParaPortion->GetTextPortions().push_back( aDummyPortion );
+
+ if ( bLineBreak )
+ {
+ // -2: The new one is already inserted.
+ const std::size_t nPos = pTEParaPortion->GetTextPortions().size() - 1;
+ aTmpLine.SetStartPortion( nPos );
+ aTmpLine.SetEndPortion( nPos );
+ }
+ pTEParaPortion->GetLines().push_back( aTmpLine );
+}
+
+void TextEngine::ImpBreakLine( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nPortionStart, tools::Long nRemainingWidth )
+{
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+
+ // Font still should be adjusted
+ sal_Int32 nMaxBreakPos = mpRefDev->GetTextBreak( pNode->GetText(), nRemainingWidth, nPortionStart );
+
+ SAL_WARN_IF( nMaxBreakPos >= pNode->GetText().getLength(), "vcl", "ImpBreakLine: Break?!" );
+
+ if ( nMaxBreakPos == -1 ) // GetTextBreak() != GetTextSize()
+ nMaxBreakPos = pNode->GetText().getLength() - 1;
+
+ uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
+ i18n::LineBreakHyphenationOptions aHyphOptions( nullptr, uno::Sequence< beans::PropertyValue >(), 1 );
+
+ i18n::LineBreakUserOptions aUserOptions;
+ aUserOptions.forbiddenBeginCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().beginLine;
+ aUserOptions.forbiddenEndCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().endLine;
+ aUserOptions.applyForbiddenRules = true;
+ aUserOptions.allowPunctuationOutsideMargin = false;
+ aUserOptions.allowHyphenateEnglish = false;
+
+ static const css::lang::Locale aDefLocale;
+ i18n::LineBreakResults aLBR = xBI->getLineBreak( pNode->GetText(), nMaxBreakPos, aDefLocale, pLine->GetStart(), aHyphOptions, aUserOptions );
+ sal_Int32 nBreakPos = aLBR.breakIndex;
+ if ( nBreakPos <= pLine->GetStart() )
+ {
+ nBreakPos = nMaxBreakPos;
+ if ( nBreakPos <= pLine->GetStart() )
+ nBreakPos = pLine->GetStart() + 1; // infinite loop otherwise!
+ }
+
+ // the damaged Portion is the End Portion
+ pLine->SetEnd( nBreakPos );
+ const std::size_t nEndPortion = SplitTextPortion( nPara, nBreakPos );
+
+ if ( nBreakPos >= pLine->GetStart() &&
+ nBreakPos < pNode->GetText().getLength() &&
+ pNode->GetText()[ nBreakPos ] == ' ' )
+ {
+ // generally suppress blanks at the end of line
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ TETextPortion& rTP = pTEParaPortion->GetTextPortions()[ nEndPortion ];
+ SAL_WARN_IF( nBreakPos <= pLine->GetStart(), "vcl", "ImpBreakLine: SplitTextPortion at beginning of line?" );
+ rTP.GetWidth() = CalcTextWidth( nPara, nBreakPos-rTP.GetLen(), rTP.GetLen()-1 );
+ }
+ pLine->SetEndPortion( nEndPortion );
+}
+
+std::size_t TextEngine::SplitTextPortion( sal_uInt32 nPara, sal_Int32 nPos )
+{
+
+ // the Portion at nPos is being split, unless there is already a switch at nPos
+ if ( nPos == 0 )
+ return 0;
+
+ std::size_t nSplitPortion;
+ sal_Int32 nTmpPos = 0;
+ TETextPortion* pTextPortion = nullptr;
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
+ for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ )
+ {
+ TETextPortion& rTP = pTEParaPortion->GetTextPortions()[nSplitPortion];
+ nTmpPos += rTP.GetLen();
+ if ( nTmpPos >= nPos )
+ {
+ if ( nTmpPos == nPos ) // nothing needs splitting
+ return nSplitPortion;
+ pTextPortion = &rTP;
+ break;
+ }
+ }
+
+ SAL_WARN_IF( !pTextPortion, "vcl", "SplitTextPortion: position outside of region!" );
+
+ const sal_Int32 nOverlapp = nTmpPos - nPos;
+ pTextPortion->GetLen() -= nOverlapp;
+ pTextPortion->GetWidth() = CalcTextWidth( nPara, nPos-pTextPortion->GetLen(), pTextPortion->GetLen() );
+ TETextPortion aNewPortion( nOverlapp );
+ pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nSplitPortion + 1, aNewPortion );
+
+ return nSplitPortion;
+}
+
+void TextEngine::CreateTextPortions( sal_uInt32 nPara, sal_Int32 nStartPos )
+{
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ TextNode* pNode = pTEParaPortion->GetNode();
+ SAL_WARN_IF( pNode->GetText().isEmpty(), "vcl", "CreateTextPortions: should not be used for empty paragraphs!" );
+
+ o3tl::sorted_vector<sal_Int32> aPositions;
+ o3tl::sorted_vector<sal_Int32>::const_iterator aPositionsIt;
+ aPositions.insert(0);
+
+ const sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
+
+ aPositions.insert( rAttrib.GetStart() );
+ aPositions.insert( rAttrib.GetEnd() );
+ }
+ aPositions.insert( pNode->GetText().getLength() );
+
+ const std::vector<TEWritingDirectionInfo>& rWritingDirections = pTEParaPortion->GetWritingDirectionInfos();
+ for ( const auto& rWritingDirection : rWritingDirections )
+ aPositions.insert( rWritingDirection.nStartPos );
+
+ if ( mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) )
+ {
+ ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xffff);
+ for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ )
+ {
+ if ( mpIMEInfos->pAttribs[n] != nLastAttr )
+ {
+ aPositions.insert( mpIMEInfos->aPos.GetIndex() + n );
+ nLastAttr = mpIMEInfos->pAttribs[n];
+ }
+ }
+ }
+
+ sal_Int32 nTabPos = pNode->GetText().indexOf( '\t' );
+ while ( nTabPos != -1 )
+ {
+ aPositions.insert( nTabPos );
+ aPositions.insert( nTabPos + 1 );
+ nTabPos = pNode->GetText().indexOf( '\t', nTabPos+1 );
+ }
+
+ // Delete starting with...
+ // Unfortunately, the number of TextPortions does not have to be
+ // equal to aPositions.Count(), because of linebreaks
+ sal_Int32 nPortionStart = 0;
+ std::size_t nInvPortion = 0;
+ std::size_t nP;
+ for ( nP = 0; nP < pTEParaPortion->GetTextPortions().size(); nP++ )
+ {
+ TETextPortion& rTmpPortion = pTEParaPortion->GetTextPortions()[nP];
+ nPortionStart += rTmpPortion.GetLen();
+ if ( nPortionStart >= nStartPos )
+ {
+ nPortionStart -= rTmpPortion.GetLen();
+ nInvPortion = nP;
+ break;
+ }
+ }
+ OSL_ENSURE(nP < pTEParaPortion->GetTextPortions().size()
+ || pTEParaPortion->GetTextPortions().empty(),
+ "CreateTextPortions: Nothing to delete!");
+ if ( nInvPortion && ( nPortionStart+pTEParaPortion->GetTextPortions()[nInvPortion].GetLen() > nStartPos ) )
+ {
+ // better one before...
+ // But only if it was within the Portion; otherwise it might be
+ // the only one in the previous line!
+ nInvPortion--;
+ nPortionStart -= pTEParaPortion->GetTextPortions()[nInvPortion].GetLen();
+ }
+ pTEParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion );
+
+ // a Portion might have been created by a line break
+ aPositions.insert( nPortionStart );
+
+ aPositionsIt = aPositions.find( nPortionStart );
+ SAL_WARN_IF( aPositionsIt == aPositions.end(), "vcl", "CreateTextPortions: nPortionStart not found" );
+
+ if ( aPositionsIt != aPositions.end() )
+ {
+ o3tl::sorted_vector<sal_Int32>::const_iterator nextIt = aPositionsIt;
+ for ( ++nextIt; nextIt != aPositions.end(); ++aPositionsIt, ++nextIt )
+ {
+ TETextPortion aNew( *nextIt - *aPositionsIt );
+ pTEParaPortion->GetTextPortions().push_back( aNew );
+ }
+ }
+ OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "CreateTextPortions: No Portions?!");
+}
+
+void TextEngine::RecalcTextPortion( sal_uInt32 nPara, sal_Int32 nStartPos, sal_Int32 nNewChars )
+{
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "RecalcTextPortion: no Portions!");
+ OSL_ENSURE(nNewChars, "RecalcTextPortion: Diff == 0");
+
+ TextNode* const pNode = pTEParaPortion->GetNode();
+ if ( nNewChars > 0 )
+ {
+ // If an Attribute is starting/ending at nStartPos, or there is a tab
+ // before nStartPos => a new Portion starts.
+ // Otherwise the Portion is extended at nStartPos.
+ // Or if at the very beginning ( StartPos 0 ) followed by a tab...
+ if ( ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) ) ||
+ ( nStartPos && ( pNode->GetText()[ nStartPos - 1 ] == '\t' ) ) ||
+ ( !nStartPos && ( nNewChars < pNode->GetText().getLength() ) && pNode->GetText()[ nNewChars ] == '\t' ) )
+ {
+ std::size_t nNewPortionPos = 0;
+ if ( nStartPos )
+ nNewPortionPos = SplitTextPortion( nPara, nStartPos ) + 1;
+
+ // Here could be an empty Portion if the paragraph was empty,
+ // or a new line was created by a hard line-break.
+ if ( ( nNewPortionPos < pTEParaPortion->GetTextPortions().size() ) &&
+ !pTEParaPortion->GetTextPortions()[nNewPortionPos].GetLen() )
+ {
+ // use the empty Portion
+ pTEParaPortion->GetTextPortions()[nNewPortionPos].GetLen() = nNewChars;
+ }
+ else
+ {
+ TETextPortion aNewPortion( nNewChars );
+ pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nNewPortionPos, aNewPortion );
+ }
+ }
+ else
+ {
+ sal_Int32 nPortionStart {0};
+ const std::size_t nTP = pTEParaPortion->GetTextPortions().FindPortion( nStartPos, nPortionStart );
+ TETextPortion& rTP = pTEParaPortion->GetTextPortions()[ nTP ];
+ rTP.GetLen() += nNewChars;
+ rTP.GetWidth() = -1;
+ }
+ }
+ else
+ {
+ // Shrink or remove Portion
+ // Before calling this function, ensure that no Portions were in the deleted range!
+
+ // There must be no Portion reaching into or starting within,
+ // thus: nStartPos <= nPos <= nStartPos - nNewChars(neg.)
+ std::size_t nPortion = 0;
+ sal_Int32 nPos = 0;
+ const sal_Int32 nEnd = nStartPos-nNewChars;
+ const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
+ TETextPortion* pTP = nullptr;
+ for ( nPortion = 0; nPortion < nPortions; nPortion++ )
+ {
+ pTP = &pTEParaPortion->GetTextPortions()[ nPortion ];
+ if ( ( nPos+pTP->GetLen() ) > nStartPos )
+ {
+ SAL_WARN_IF( nPos > nStartPos, "vcl", "RecalcTextPortion: Bad Start!" );
+ SAL_WARN_IF( nPos+pTP->GetLen() < nEnd, "vcl", "RecalcTextPortion: Bad End!" );
+ break;
+ }
+ nPos += pTP->GetLen();
+ }
+ SAL_WARN_IF( !pTP, "vcl", "RecalcTextPortion: Portion not found!" );
+ if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
+ {
+ // remove Portion
+ pTEParaPortion->GetTextPortions().erase( pTEParaPortion->GetTextPortions().begin() + nPortion );
+ }
+ else
+ {
+ SAL_WARN_IF( pTP->GetLen() <= (-nNewChars), "vcl", "RecalcTextPortion: Portion too small to shrink!" );
+ pTP->GetLen() += nNewChars;
+ }
+ OSL_ENSURE( pTEParaPortion->GetTextPortions().size(),
+ "RecalcTextPortion: none are left!" );
+ }
+}
+
+void TextEngine::ImpPaint( OutputDevice* pOutDev, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection )
+{
+ if ( !GetUpdateMode() )
+ return;
+
+ if ( !IsFormatted() )
+ FormatDoc();
+
+ vcl::Window* const pOutWin = pOutDev->GetOwnerWindow();
+ const bool bTransparent = (pOutWin && pOutWin->IsPaintTransparent());
+
+ tools::Long nY = rStartPos.Y();
+
+ TextPaM const* pSelStart = nullptr;
+ TextPaM const* pSelEnd = nullptr;
+ if ( pSelection && pSelection->HasRange() )
+ {
+ const bool bInvers = pSelection->GetEnd() < pSelection->GetStart();
+ pSelStart = !bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
+ pSelEnd = bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
+ }
+
+ const StyleSettings& rStyleSettings = pOutDev->GetSettings().GetStyleSettings();
+
+ // for all paragraphs
+ for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
+ {
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
+ // in case while typing Idle-Formatting, asynchronous Paint
+ if ( pPortion->IsInvalid() )
+ return;
+
+ const tools::Long nParaHeight = CalcParaHeight( nPara );
+ if ( !pPaintArea || ( ( nY + nParaHeight ) > pPaintArea->Top() ) )
+ {
+ // for all lines of the paragraph
+ sal_Int32 nIndex = 0;
+ for ( auto & rLine : pPortion->GetLines() )
+ {
+ Point aTmpPos( rStartPos.X() + rLine.GetStartX(), nY );
+
+ if ( !pPaintArea || ( ( nY + mnCharHeight ) > pPaintArea->Top() ) )
+ {
+ // for all Portions of the line
+ nIndex = rLine.GetStart();
+ for ( std::size_t y = rLine.GetStartPortion(); y <= rLine.GetEndPortion(); y++ )
+ {
+ OSL_ENSURE(pPortion->GetTextPortions().size(),
+ "ImpPaint: Line without Textportion!");
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ y ];
+
+ ImpInitLayoutMode( pOutDev /*, pTextPortion->IsRightToLeft() */);
+
+ const tools::Long nTxtWidth = rTextPortion.GetWidth();
+ aTmpPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nIndex, nIndex ) );
+
+ // only print if starting in the visible region
+ if ( ( aTmpPos.X() + nTxtWidth ) >= 0 )
+ {
+ switch ( rTextPortion.GetKind() )
+ {
+ case PORTIONKIND_TEXT:
+ {
+ vcl::Font aFont;
+ SeekCursor( nPara, nIndex+1, aFont, pOutDev );
+ if( bTransparent )
+ aFont.SetTransparent( true );
+ else if ( pSelection )
+ aFont.SetTransparent( false );
+ pOutDev->SetFont( aFont );
+
+ sal_Int32 nTmpIndex = nIndex;
+ sal_Int32 nEnd = nTmpIndex + rTextPortion.GetLen();
+ Point aPos = aTmpPos;
+
+ bool bDone = false;
+ if ( pSelStart )
+ {
+ // is a part of it in the selection?
+ const TextPaM aTextStart( nPara, nTmpIndex );
+ const TextPaM aTextEnd( nPara, nEnd );
+ if ( ( aTextStart < *pSelEnd ) && ( aTextEnd > *pSelStart ) )
+ {
+ // 1) vcl::Region before Selection
+ if ( aTextStart < *pSelStart )
+ {
+ const sal_Int32 nL = pSelStart->GetIndex() - nTmpIndex;
+ pOutDev->SetFont( aFont);
+ pOutDev->SetTextFillColor();
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
+ nTmpIndex = nTmpIndex + nL;
+
+ }
+ // 2) vcl::Region with Selection
+ sal_Int32 nL = nEnd - nTmpIndex;
+ if ( aTextEnd > *pSelEnd )
+ nL = pSelEnd->GetIndex() - nTmpIndex;
+ if ( nL )
+ {
+ const Color aOldTextColor = pOutDev->GetTextColor();
+ pOutDev->SetTextColor( rStyleSettings.GetHighlightTextColor() );
+ pOutDev->SetTextFillColor( rStyleSettings.GetHighlightColor() );
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
+ pOutDev->SetTextColor( aOldTextColor );
+ pOutDev->SetTextFillColor();
+ nTmpIndex = nTmpIndex + nL;
+ }
+
+ // 3) vcl::Region after Selection
+ if ( nTmpIndex < nEnd )
+ {
+ nL = nEnd-nTmpIndex;
+ pOutDev->SetTextFillColor();
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
+ }
+ bDone = true;
+ }
+ }
+ if ( !bDone )
+ {
+ pOutDev->SetTextFillColor();
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nEnd ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
+ }
+ }
+ break;
+ case PORTIONKIND_TAB:
+ // for HideSelection() only Range, pSelection = 0.
+ if ( pSelStart ) // also implies pSelEnd
+ {
+ const tools::Rectangle aTabArea( aTmpPos, Point( aTmpPos.X()+nTxtWidth, aTmpPos.Y()+mnCharHeight-1 ) );
+ // is the Tab in the Selection???
+ const TextPaM aTextStart(nPara, nIndex);
+ const TextPaM aTextEnd(nPara, nIndex + 1);
+ if ((aTextStart < *pSelEnd) && (aTextEnd > *pSelStart))
+ {
+ const Color aOldColor = pOutDev->GetFillColor();
+ pOutDev->SetFillColor(
+ rStyleSettings.GetHighlightColor());
+ pOutDev->DrawRect(aTabArea);
+ pOutDev->SetFillColor(aOldColor);
+ }
+ else
+ {
+ pOutDev->Erase( aTabArea );
+ }
+ }
+ break;
+ default:
+ OSL_FAIL( "ImpPaint: Unknown Portion-Type !" );
+ }
+ }
+
+ nIndex += rTextPortion.GetLen();
+ }
+ }
+
+ nY += mnCharHeight;
+
+ if ( pPaintArea && ( nY >= pPaintArea->Bottom() ) )
+ break; // no more visible actions
+ }
+ }
+ else
+ {
+ nY += nParaHeight;
+ }
+
+ if ( pPaintArea && ( nY > pPaintArea->Bottom() ) )
+ break; // no more visible actions
+ }
+}
+
+bool TextEngine::CreateLines( sal_uInt32 nPara )
+{
+ // bool: changing Height of Paragraph Yes/No - true/false
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ SAL_WARN_IF( !pTEParaPortion->IsInvalid(), "vcl", "CreateLines: Portion not invalid!" );
+
+ const auto nOldLineCount = pTEParaPortion->GetLines().size();
+
+ // fast special case for empty paragraphs
+ if ( pTEParaPortion->GetNode()->GetText().isEmpty() )
+ {
+ if ( !pTEParaPortion->GetTextPortions().empty() )
+ pTEParaPortion->GetTextPortions().Reset();
+ pTEParaPortion->GetLines().clear();
+ CreateAndInsertEmptyLine( nPara );
+ pTEParaPortion->SetValid();
+ return nOldLineCount != pTEParaPortion->GetLines().size();
+ }
+
+ // initialization
+ if ( pTEParaPortion->GetLines().empty() )
+ {
+ pTEParaPortion->GetLines().emplace_back( );
+ }
+
+ const sal_Int32 nInvalidDiff = pTEParaPortion->GetInvalidDiff();
+ const sal_Int32 nInvalidStart = pTEParaPortion->GetInvalidPosStart();
+ const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff );
+ bool bQuickFormat = false;
+
+ if ( pTEParaPortion->GetWritingDirectionInfos().empty() )
+ ImpInitWritingDirections( nPara );
+
+ if ( pTEParaPortion->GetWritingDirectionInfos().size() == 1 && pTEParaPortion->IsSimpleInvalid() )
+ {
+ bQuickFormat = nInvalidDiff != 0;
+ if ( nInvalidDiff < 0 )
+ {
+ // check if deleting across Portion border
+ sal_Int32 nPos = 0;
+ for ( const auto & rTP : pTEParaPortion->GetTextPortions() )
+ {
+ // there must be no Start/End in the deleted region
+ nPos += rTP.GetLen();
+ if ( nPos > nInvalidStart && nPos < nInvalidEnd )
+ {
+ bQuickFormat = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( bQuickFormat )
+ RecalcTextPortion( nPara, nInvalidStart, nInvalidDiff );
+ else
+ CreateTextPortions( nPara, nInvalidStart );
+
+ // search for line with InvalidPos; start a line prior
+ // flag lines => do not remove!
+
+ sal_uInt16 nLine = pTEParaPortion->GetLines().size()-1;
+ for ( sal_uInt16 nL = 0; nL <= nLine; nL++ )
+ {
+ TextLine& rLine = pTEParaPortion->GetLines()[ nL ];
+ if ( rLine.GetEnd() > nInvalidStart )
+ {
+ nLine = nL;
+ break;
+ }
+ rLine.SetValid();
+ }
+ // start a line before...
+ // if typing at the end, the line before cannot change
+ if ( nLine && ( !pTEParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->GetText().getLength() ) || ( nInvalidDiff <= 0 ) ) )
+ nLine--;
+
+ TextLine* pLine = &( pTEParaPortion->GetLines()[ nLine ] );
+
+ // format all lines starting here
+ std::size_t nDelFromLine = TETextPortionList::npos;
+
+ sal_Int32 nIndex = pLine->GetStart();
+ TextLine aSaveLine( *pLine );
+
+ while ( nIndex < pNode->GetText().getLength() )
+ {
+ bool bEOL = false;
+ sal_Int32 nPortionStart = 0;
+ sal_Int32 nPortionEnd = 0;
+
+ sal_Int32 nTmpPos = nIndex;
+ std::size_t nTmpPortion = pLine->GetStartPortion();
+ tools::Long nTmpWidth = mpDoc->GetLeftMargin();
+ // do not subtract margin; it is included in TmpWidth
+ tools::Long nXWidth = std::max(
+ mnMaxTextWidth ? mnMaxTextWidth : std::numeric_limits<tools::Long>::max(), nTmpWidth);
+
+ // search for Portion that does not fit anymore into line
+ TETextPortion* pPortion = nullptr;
+ bool bBrokenLine = false;
+
+ while ( ( nTmpWidth <= nXWidth ) && !bEOL && ( nTmpPortion < pTEParaPortion->GetTextPortions().size() ) )
+ {
+ nPortionStart = nTmpPos;
+ pPortion = &pTEParaPortion->GetTextPortions()[ nTmpPortion ];
+ SAL_WARN_IF( !pPortion->GetLen(), "vcl", "CreateLines: Empty Portion!" );
+ if ( pNode->GetText()[ nTmpPos ] == '\t' )
+ {
+ tools::Long nCurPos = nTmpWidth-mpDoc->GetLeftMargin();
+ nTmpWidth = ((nCurPos/mnDefTab)+1)*mnDefTab+mpDoc->GetLeftMargin();
+ pPortion->GetWidth() = nTmpWidth - nCurPos - mpDoc->GetLeftMargin();
+ // infinite loop, if this is the first token of the line and nTmpWidth > aPaperSize.Width !!!
+ if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
+ {
+ // adjust Tab
+ pPortion->GetWidth() = nXWidth-1;
+ nTmpWidth = pPortion->GetWidth();
+ bEOL = true;
+ bBrokenLine = true;
+ }
+ pPortion->GetKind() = PORTIONKIND_TAB;
+ }
+ else
+ {
+
+ pPortion->GetWidth() = CalcTextWidth( nPara, nTmpPos, pPortion->GetLen() );
+ nTmpWidth += pPortion->GetWidth();
+
+ pPortion->SetRightToLeft( ImpGetRightToLeft( nPara, nTmpPos+1 ) );
+ pPortion->GetKind() = PORTIONKIND_TEXT;
+ }
+
+ nTmpPos += pPortion->GetLen();
+ nPortionEnd = nTmpPos;
+ nTmpPortion++;
+ }
+
+ // this was perhaps one Portion too far
+ bool bFixedEnd = false;
+ if ( nTmpWidth > nXWidth )
+ {
+ nPortionEnd = nTmpPos;
+ nTmpPos -= pPortion->GetLen();
+ nPortionStart = nTmpPos;
+ nTmpPortion--;
+ bEOL = false;
+
+ nTmpWidth -= pPortion->GetWidth();
+ if ( pPortion->GetKind() == PORTIONKIND_TAB )
+ {
+ bEOL = true;
+ bFixedEnd = true;
+ }
+ }
+ else
+ {
+ bEOL = true;
+ pLine->SetEnd( nPortionEnd );
+ OSL_ENSURE(pTEParaPortion->GetTextPortions().size(),
+ "CreateLines: No TextPortions?");
+ pLine->SetEndPortion( pTEParaPortion->GetTextPortions().size() - 1 );
+ }
+
+ if ( bFixedEnd )
+ {
+ pLine->SetEnd( nPortionStart );
+ pLine->SetEndPortion( nTmpPortion-1 );
+ }
+ else if ( bBrokenLine )
+ {
+ pLine->SetEnd( nPortionStart+1 );
+ pLine->SetEndPortion( nTmpPortion-1 );
+ }
+ else if ( !bEOL )
+ {
+ SAL_WARN_IF( (nPortionEnd-nPortionStart) != pPortion->GetLen(), "vcl", "CreateLines: There is a Portion after all?!" );
+ const tools::Long nRemainingWidth = mnMaxTextWidth - nTmpWidth;
+ ImpBreakLine( nPara, pLine, nPortionStart, nRemainingWidth );
+ }
+
+ if ( ( ImpGetAlign() == TxtAlign::Center ) || ( ImpGetAlign() == TxtAlign::Right ) )
+ {
+ // adjust
+ tools::Long nTextWidth = 0;
+ for ( std::size_t nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ )
+ {
+ TETextPortion& rTextPortion = pTEParaPortion->GetTextPortions()[ nTP ];
+ nTextWidth += rTextPortion.GetWidth();
+ }
+ const tools::Long nSpace = mnMaxTextWidth - nTextWidth;
+ if ( nSpace > 0 )
+ {
+ if ( ImpGetAlign() == TxtAlign::Center )
+ pLine->SetStartX( static_cast<sal_uInt16>(nSpace / 2) );
+ else // TxtAlign::Right
+ pLine->SetStartX( static_cast<sal_uInt16>(nSpace) );
+ }
+ }
+ else
+ {
+ pLine->SetStartX( mpDoc->GetLeftMargin() );
+ }
+
+ // check if the line has to be printed again
+ pLine->SetInvalid();
+
+ if ( pTEParaPortion->IsSimpleInvalid() )
+ {
+ // Change due to simple TextChange...
+ // Do not abort formatting, as Portions might have to be split!
+ // Once it is ok to abort, then validate the following lines!
+ // But mark as valid, thus reduce printing...
+ if ( pLine->GetEnd() < nInvalidStart )
+ {
+ if ( *pLine == aSaveLine )
+ {
+ pLine->SetValid();
+ }
+ }
+ else
+ {
+ const sal_Int32 nStart = pLine->GetStart();
+ const sal_Int32 nEnd = pLine->GetEnd();
+
+ if ( nStart > nInvalidEnd )
+ {
+ if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) &&
+ ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) )
+ {
+ pLine->SetValid();
+ if ( bQuickFormat )
+ {
+ pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
+ break;
+ }
+ }
+ }
+ else if ( bQuickFormat && ( nEnd > nInvalidEnd) )
+ {
+ // If the invalid line ends such that the next line starts
+ // at the 'same' position as before (no change in line breaks),
+ // the text width does not have to be recalculated.
+ if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) )
+ {
+ pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
+ break;
+ }
+ }
+ }
+ }
+
+ nIndex = pLine->GetEnd(); // next line Start = previous line End
+ // because nEnd is past the last char!
+
+ const std::size_t nEndPortion = pLine->GetEndPortion();
+
+ // next line or new line
+ pLine = nullptr;
+ if ( nLine < pTEParaPortion->GetLines().size()-1 )
+ pLine = &( pTEParaPortion->GetLines()[ ++nLine ] );
+ if ( pLine && ( nIndex >= pNode->GetText().getLength() ) )
+ {
+ nDelFromLine = nLine;
+ break;
+ }
+ if ( !pLine )
+ {
+ if ( nIndex < pNode->GetText().getLength() )
+ {
+ ++nLine;
+ pTEParaPortion->GetLines().insert( pTEParaPortion->GetLines().begin() + nLine, TextLine() );
+ pLine = &pTEParaPortion->GetLines()[nLine];
+ }
+ else
+ {
+ break;
+ }
+ }
+ aSaveLine = *pLine;
+ pLine->SetStart( nIndex );
+ pLine->SetEnd( nIndex );
+ pLine->SetStartPortion( nEndPortion+1 );
+ pLine->SetEndPortion( nEndPortion+1 );
+
+ } // while ( Index < Len )
+
+ if (nDelFromLine != TETextPortionList::npos)
+ {
+ pTEParaPortion->GetLines().erase( pTEParaPortion->GetLines().begin() + nDelFromLine,
+ pTEParaPortion->GetLines().end() );
+ }
+
+ SAL_WARN_IF( pTEParaPortion->GetLines().empty(), "vcl", "CreateLines: No Line!" );
+
+ pTEParaPortion->SetValid();
+
+ return nOldLineCount != pTEParaPortion->GetLines().size();
+}
+
+OUString TextEngine::GetWord( const TextPaM& rCursorPos, TextPaM* pStartOfWord, TextPaM* pEndOfWord )
+{
+ OUString aWord;
+ if ( rCursorPos.GetPara() < mpDoc->GetNodes().size() )
+ {
+ TextSelection aSel( rCursorPos );
+ TextNode* pNode = mpDoc->GetNodes()[ rCursorPos.GetPara() ].get();
+ uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
+ i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), rCursorPos.GetIndex(), GetLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ // tdf#57879 - expand selection to the left to include connector punctuations and search for additional word boundaries
+ if (aBoundary.startPos > 0 && aBoundary.startPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.startPos]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.startPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.startPos - 1,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).startPos;
+ }
+ while (aBoundary.startPos > 0 && u_charType(pNode->GetText()[aBoundary.startPos - 1]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.startPos = std::min(aBoundary.startPos,
+ xBI->getWordBoundary( pNode->GetText(), aBoundary.startPos - 2,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).startPos);
+ }
+ // tdf#57879 - expand selection to the right to include connector punctuations and search for additional word boundaries
+ if (aBoundary.endPos > 0 && aBoundary.endPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.endPos - 1]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.endPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.endPos,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).endPos;
+ }
+ while (aBoundary.endPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.endPos]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.endPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.endPos + 1,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).endPos;
+ }
+ aSel.GetStart().GetIndex() = aBoundary.startPos;
+ aSel.GetEnd().GetIndex() = aBoundary.endPos;
+ aWord = pNode->GetText().copy( aSel.GetStart().GetIndex(), aSel.GetEnd().GetIndex() - aSel.GetStart().GetIndex() );
+ if ( pStartOfWord )
+ *pStartOfWord = aSel.GetStart();
+ if (pEndOfWord)
+ *pEndOfWord = aSel.GetEnd();
+ }
+ return aWord;
+}
+
+bool TextEngine::Read( SvStream& rInput, const TextSelection* pSel )
+{
+ const bool bUpdate = GetUpdateMode();
+ SetUpdateMode( false );
+
+ UndoActionStart();
+ TextSelection aSel;
+ if ( pSel )
+ aSel = *pSel;
+ else
+ {
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ TextNode* pNode = mpDoc->GetNodes()[ nParas - 1 ].get();
+ aSel = TextPaM( nParas-1 , pNode->GetText().getLength() );
+ }
+
+ if ( aSel.HasRange() )
+ aSel = ImpDeleteText( aSel );
+
+ OStringBuffer aLine;
+ bool bDone = rInput.ReadLine( aLine );
+ OUString aTmpStr(OStringToOUString(aLine, rInput.GetStreamCharSet()));
+ while ( bDone )
+ {
+ aSel = ImpInsertText( aSel, aTmpStr );
+ bDone = rInput.ReadLine( aLine );
+ aTmpStr = OStringToOUString(aLine, rInput.GetStreamCharSet());
+ if ( bDone )
+ aSel = ImpInsertParaBreak( aSel.GetEnd() );
+ }
+
+ UndoActionEnd();
+
+ const TextSelection aNewSel( aSel.GetEnd(), aSel.GetEnd() );
+
+ // so that FormatAndUpdate does not access the invalid selection
+ if ( GetActiveView() )
+ GetActiveView()->ImpSetSelection( aNewSel );
+
+ SetUpdateMode( bUpdate );
+ FormatAndUpdate( GetActiveView() );
+
+ return rInput.GetError() == ERRCODE_NONE;
+}
+
+void TextEngine::Write( SvStream& rOutput )
+{
+ TextSelection aSel;
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ TextNode* pSelNode = mpDoc->GetNodes()[ nParas - 1 ].get();
+ aSel.GetStart() = TextPaM( 0, 0 );
+ aSel.GetEnd() = TextPaM( nParas-1, pSelNode->GetText().getLength() );
+
+ for ( sal_uInt32 nPara = aSel.GetStart().GetPara(); nPara <= aSel.GetEnd().GetPara(); ++nPara )
+ {
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+
+ const sal_Int32 nStartPos = nPara == aSel.GetStart().GetPara()
+ ? aSel.GetStart().GetIndex() : 0;
+ const sal_Int32 nEndPos = nPara == aSel.GetEnd().GetPara()
+ ? aSel.GetEnd().GetIndex() : pNode->GetText().getLength();
+
+ const OUString aText = pNode->GetText().copy( nStartPos, nEndPos-nStartPos );
+ rOutput.WriteLine(OUStringToOString(aText, rOutput.GetStreamCharSet()));
+ }
+}
+
+void TextEngine::RemoveAttribs( sal_uInt32 nPara )
+{
+ if ( nPara >= mpDoc->GetNodes().size() )
+ return;
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ if ( pNode->GetCharAttribs().Count() )
+ {
+ pNode->GetCharAttribs().Clear();
+
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ pTEParaPortion->MarkSelectionInvalid( 0 );
+
+ mbFormatted = false;
+
+ IdleFormatAndUpdate( nullptr, 0xFFFF );
+ }
+}
+
+void TextEngine::SetAttrib( const TextAttrib& rAttr, sal_uInt32 nPara, sal_Int32 nStart, sal_Int32 nEnd )
+{
+
+ // For now do not check if Attributes overlap!
+ // This function is for TextEditors that want to _quickly_ generate the Syntax-Highlight
+
+ // As TextEngine is currently intended only for TextEditors, there is no Undo for Attributes!
+
+ if ( nPara >= mpDoc->GetNodes().size() )
+ return;
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ const sal_Int32 nMax = pNode->GetText().getLength();
+ if ( nStart > nMax )
+ nStart = nMax;
+ if ( nEnd > nMax )
+ nEnd = nMax;
+
+ pNode->GetCharAttribs().InsertAttrib( std::make_unique<TextCharAttrib>( rAttr, nStart, nEnd ) );
+ pTEParaPortion->MarkSelectionInvalid( nStart );
+
+ mbFormatted = false;
+ IdleFormatAndUpdate( nullptr, 0xFFFF );
+}
+
+void TextEngine::SetTextAlign( TxtAlign eAlign )
+{
+ if ( eAlign != meAlign )
+ {
+ meAlign = eAlign;
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+void TextEngine::ValidateSelection( TextSelection& rSel ) const
+{
+ ValidatePaM( rSel.GetStart() );
+ ValidatePaM( rSel.GetEnd() );
+}
+
+void TextEngine::ValidatePaM( TextPaM& rPaM ) const
+{
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ if ( rPaM.GetPara() >= nParas )
+ {
+ rPaM.GetPara() = nParas ? nParas-1 : 0;
+ rPaM.GetIndex() = TEXT_INDEX_ALL;
+ }
+
+ const sal_Int32 nMaxIndex = GetTextLen( rPaM.GetPara() );
+ if ( rPaM.GetIndex() > nMaxIndex )
+ rPaM.GetIndex() = nMaxIndex;
+}
+
+// adjust State & Selection
+
+void TextEngine::ImpParagraphInserted( sal_uInt32 nPara )
+{
+ // No adjustment needed for the active View;
+ // but for all passive Views the Selection needs adjusting.
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() >= nPara )
+ rPaM.GetPara()++;
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaInserted, nPara ) );
+}
+
+void TextEngine::ImpParagraphRemoved( sal_uInt32 nPara )
+{
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() > nPara )
+ rPaM.GetPara()--;
+ else if ( rPaM.GetPara() == nPara )
+ {
+ rPaM.GetIndex() = 0;
+ if ( rPaM.GetPara() >= nParas )
+ rPaM.GetPara()--;
+ }
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaRemoved, nPara ) );
+}
+
+void TextEngine::ImpCharsRemoved( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
+{
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ const sal_Int32 nEnd = nPos + nChars;
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() == nPara )
+ {
+ if ( rPaM.GetIndex() > nEnd )
+ rPaM.GetIndex() = rPaM.GetIndex() - nChars;
+ else if ( rPaM.GetIndex() > nPos )
+ rPaM.GetIndex() = nPos;
+ }
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
+}
+
+void TextEngine::ImpCharsInserted( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
+{
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() == nPara )
+ {
+ if ( rPaM.GetIndex() >= nPos )
+ rPaM.GetIndex() += nChars;
+ }
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
+}
+
+void TextEngine::Draw( OutputDevice* pDev, const Point& rPos )
+{
+ ImpPaint( pDev, rPos, nullptr );
+}
+
+void TextEngine::SetLeftMargin( sal_uInt16 n )
+{
+ mpDoc->SetLeftMargin( n );
+}
+
+uno::Reference< i18n::XBreakIterator > const & TextEngine::GetBreakIterator()
+{
+ if ( !mxBreakIterator.is() )
+ mxBreakIterator = vcl::unohelper::CreateBreakIterator();
+ SAL_WARN_IF( !mxBreakIterator.is(), "vcl", "BreakIterator: Failed to create!" );
+ return mxBreakIterator;
+}
+
+void TextEngine::SetLocale( const css::lang::Locale& rLocale )
+{
+ maLocale = rLocale;
+ mpLocaleDataWrapper.reset();
+}
+
+css::lang::Locale const & TextEngine::GetLocale()
+{
+ if ( maLocale.Language.isEmpty() )
+ {
+ maLocale = Application::GetSettings().GetUILanguageTag().getLocale(); // TODO: why UI locale?
+ }
+ return maLocale;
+}
+
+LocaleDataWrapper* TextEngine::ImpGetLocaleDataWrapper()
+{
+ if ( !mpLocaleDataWrapper )
+ mpLocaleDataWrapper.reset( new LocaleDataWrapper( LanguageTag( GetLocale()) ) );
+
+ return mpLocaleDataWrapper.get();
+}
+
+void TextEngine::SetRightToLeft( bool bR2L )
+{
+ if ( mbRightToLeft != bR2L )
+ {
+ mbRightToLeft = bR2L;
+ meAlign = bR2L ? TxtAlign::Right : TxtAlign::Left;
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+void TextEngine::ImpInitWritingDirections( sal_uInt32 nPara )
+{
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+ std::vector<TEWritingDirectionInfo>& rInfos = pParaPortion->GetWritingDirectionInfos();
+ rInfos.clear();
+
+ if ( !pParaPortion->GetNode()->GetText().isEmpty() )
+ {
+ const UBiDiLevel nBidiLevel = IsRightToLeft() ? 1 /*RTL*/ : 0 /*LTR*/;
+ OUString aText( pParaPortion->GetNode()->GetText() );
+
+ // Bidi functions from icu 2.0
+
+ UErrorCode nError = U_ZERO_ERROR;
+ UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError );
+ nError = U_ZERO_ERROR;
+
+ ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError );
+ nError = U_ZERO_ERROR;
+
+ tools::Long nCount = ubidi_countRuns( pBidi, &nError );
+
+ int32_t nStart = 0;
+ int32_t nEnd;
+ UBiDiLevel nCurrDir;
+
+ for ( tools::Long nIdx = 0; nIdx < nCount; ++nIdx )
+ {
+ ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
+ // bit 0 of nCurrDir indicates direction
+ rInfos.emplace_back( /*bLeftToRight*/ nCurrDir % 2 == 0, nStart, nEnd );
+ nStart = nEnd;
+ }
+
+ ubidi_close( pBidi );
+ }
+
+ // No infos mean no CTL and default dir is L2R...
+ if ( rInfos.empty() )
+ rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->GetText().getLength() );
+
+}
+
+bool TextEngine::ImpGetRightToLeft( sal_uInt32 nPara, sal_Int32 nPos )
+{
+ bool bRightToLeft = false;
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ if ( pNode && !pNode->GetText().isEmpty() )
+ {
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+ if ( pParaPortion->GetWritingDirectionInfos().empty() )
+ ImpInitWritingDirections( nPara );
+
+ std::vector<TEWritingDirectionInfo>& rDirInfos = pParaPortion->GetWritingDirectionInfos();
+ for ( const auto& rWritingDirectionInfo : rDirInfos )
+ {
+ if ( rWritingDirectionInfo.nStartPos <= nPos && rWritingDirectionInfo.nEndPos >= nPos )
+ {
+ bRightToLeft = !rWritingDirectionInfo.bLeftToRight;
+ break;
+ }
+ }
+ }
+ return bRightToLeft;
+}
+
+tools::Long TextEngine::ImpGetPortionXOffset( sal_uInt32 nPara, TextLine const * pLine, std::size_t nTextPortion )
+{
+ tools::Long nX = pLine->GetStartX();
+
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ for ( std::size_t i = pLine->GetStartPortion(); i < nTextPortion; i++ )
+ {
+ TETextPortion& rPortion = pParaPortion->GetTextPortions()[ i ];
+ nX += rPortion.GetWidth();
+ }
+
+ TETextPortion& rDestPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
+ if ( rDestPortion.GetKind() != PORTIONKIND_TAB )
+ {
+ if ( !IsRightToLeft() && rDestPortion.IsRightToLeft() )
+ {
+ // Portions behind must be added, visual before this portion
+ std::size_t nTmpPortion = nTextPortion+1;
+ while ( nTmpPortion <= pLine->GetEndPortion() )
+ {
+ TETextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX += rNextTextPortion.GetWidth();
+ else
+ break;
+ nTmpPortion++;
+ }
+ // Portions before must be removed, visual behind this portion
+ nTmpPortion = nTextPortion;
+ while ( nTmpPortion > pLine->GetStartPortion() )
+ {
+ --nTmpPortion;
+ TETextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX -= rPrevTextPortion.GetWidth();
+ else
+ break;
+ }
+ }
+ else if ( IsRightToLeft() && !rDestPortion.IsRightToLeft() )
+ {
+ // Portions behind must be removed, visual behind this portion
+ std::size_t nTmpPortion = nTextPortion+1;
+ while ( nTmpPortion <= pLine->GetEndPortion() )
+ {
+ TETextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX += rNextTextPortion.GetWidth();
+ else
+ break;
+ nTmpPortion++;
+ }
+ // Portions before must be added, visual before this portion
+ nTmpPortion = nTextPortion;
+ while ( nTmpPortion > pLine->GetStartPortion() )
+ {
+ --nTmpPortion;
+ TETextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX -= rPrevTextPortion.GetWidth();
+ else
+ break;
+ }
+ }
+ }
+
+ return nX;
+}
+
+void TextEngine::ImpInitLayoutMode( OutputDevice* pOutDev )
+{
+ vcl::text::ComplexTextLayoutFlags nLayoutMode = pOutDev->GetLayoutMode();
+
+ nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags(vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong );
+
+ pOutDev->SetLayoutMode( nLayoutMode );
+}
+
+TxtAlign TextEngine::ImpGetAlign() const
+{
+ TxtAlign eAlign = meAlign;
+ if ( IsRightToLeft() )
+ {
+ if ( eAlign == TxtAlign::Left )
+ eAlign = TxtAlign::Right;
+ else if ( eAlign == TxtAlign::Right )
+ eAlign = TxtAlign::Left;
+ }
+ return eAlign;
+}
+
+tools::Long TextEngine::ImpGetOutputOffset( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, sal_Int32 nIndex2 )
+{
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
+
+ sal_Int32 nPortionStart {0};
+ const std::size_t nPortion = pPortion->GetTextPortions().FindPortion( nIndex, nPortionStart, true );
+
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ nPortion ];
+
+ tools::Long nX;
+
+ if ( ( nIndex == nPortionStart ) && ( nIndex == nIndex2 ) )
+ {
+ // Output of full portion, so we need portion x offset.
+ // Use ImpGetPortionXOffset, because GetXPos may deliver left or right position from portion, depending on R2L, L2R
+ nX = ImpGetPortionXOffset( nPara, pLine, nPortion );
+ if ( IsRightToLeft() )
+ {
+ nX = -nX - rTextPortion.GetWidth();
+ }
+ }
+ else
+ {
+ nX = ImpGetXPos( nPara, pLine, nIndex, nIndex == nPortionStart );
+ if ( nIndex2 != nIndex )
+ {
+ const tools::Long nX2 = ImpGetXPos( nPara, pLine, nIndex2 );
+ if ( ( !IsRightToLeft() && ( nX2 < nX ) ) ||
+ ( IsRightToLeft() && ( nX2 > nX ) ) )
+ {
+ nX = nX2;
+ }
+ }
+ if ( IsRightToLeft() )
+ {
+ nX = -nX;
+ }
+ }
+
+ return nX;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */