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