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