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 21 #include <vcl/svapp.hxx> 22 #include <vcl/metaact.hxx> 23 #include <vcl/gdimtf.hxx> 24 #include <vcl/settings.hxx> 25 26 #include <editeng/adjustitem.hxx> 27 #include <editeng/tstpitem.hxx> 28 #include <editeng/lspcitem.hxx> 29 #include <editeng/flditem.hxx> 30 #include <editeng/forbiddenruleitem.hxx> 31 #include "impedit.hxx" 32 #include <editeng/editeng.hxx> 33 #include <editeng/editview.hxx> 34 #include <editeng/txtrange.hxx> 35 #include <editeng/colritem.hxx> 36 #include <editeng/udlnitem.hxx> 37 #include <editeng/fhgtitem.hxx> 38 #include <editeng/kernitem.hxx> 39 #include <editeng/lrspitem.hxx> 40 #include <editeng/ulspitem.hxx> 41 #include <editeng/fontitem.hxx> 42 #include <editeng/wghtitem.hxx> 43 #include <editeng/postitem.hxx> 44 #include <editeng/langitem.hxx> 45 #include <editeng/scriptspaceitem.hxx> 46 #include <editeng/charscaleitem.hxx> 47 #include <editeng/numitem.hxx> 48 #include <editeng/justifyitem.hxx> 49 50 #include <svtools/colorcfg.hxx> 51 #include <svl/ctloptions.hxx> 52 #include <svl/asiancfg.hxx> 53 54 #include <editeng/hngpnctitem.hxx> 55 #include <editeng/forbiddencharacterstable.hxx> 56 57 #include <unotools/configmgr.hxx> 58 #include <unotools/localedatawrapper.hxx> 59 60 #include <editeng/unolingu.hxx> 61 62 #include <set> 63 #include <math.h> 64 #include <vcl/metric.hxx> 65 #include <com/sun/star/i18n/BreakIterator.hpp> 66 #include <com/sun/star/i18n/ScriptType.hpp> 67 #include <com/sun/star/i18n/InputSequenceChecker.hpp> 68 #include <com/sun/star/text/CharacterCompressionType.hpp> 69 #include <vcl/pdfextoutdevdata.hxx> 70 #include <i18nlangtag/mslangid.hxx> 71 72 #include <comphelper/processfactory.hxx> 73 #include <rtl/ustrbuf.hxx> 74 #include <sal/log.hxx> 75 #include <osl/diagnose.h> 76 #include <comphelper/string.hxx> 77 #include <comphelper/lok.hxx> 78 #include <memory> 79 80 #include <vcl/outdev/ScopedStates.hxx> 81 82 using namespace ::com::sun::star; 83 using namespace ::com::sun::star::uno; 84 using namespace ::com::sun::star::beans; 85 using namespace ::com::sun::star::linguistic2; 86 87 #define CH_HYPH '-' 88 89 #define WRONG_SHOW_MIN 5 90 91 struct TabInfo 92 { 93 bool bValid; 94 95 SvxTabStop aTabStop; 96 sal_Int32 nTabPortion; 97 long nStartPosX; 98 long nTabPos; 99 100 TabInfo() 101 : bValid(false) 102 , nTabPortion(0) 103 , nStartPosX(0) 104 , nTabPos(0) 105 { } 106 107 }; 108 109 Point Rotate( const Point& rPoint, short nOrientation, const Point& rOrigin ) 110 { 111 double nRealOrientation = nOrientation*F_PI1800; 112 double nCos = cos( nRealOrientation ); 113 double nSin = sin( nRealOrientation ); 114 115 Point aRotatedPos; 116 Point aTranslatedPos( rPoint ); 117 118 // Translation 119 aTranslatedPos -= rOrigin; 120 121 // Rotation... 122 aRotatedPos.setX( static_cast<long>( nCos*aTranslatedPos.X() + nSin*aTranslatedPos.Y() ) ); 123 aRotatedPos.setY( static_cast<long>(- ( nSin*aTranslatedPos.X() - nCos*aTranslatedPos.Y() )) ); 124 aTranslatedPos = aRotatedPos; 125 126 // Translation... 127 aTranslatedPos += rOrigin; 128 return aTranslatedPos; 129 } 130 131 AsianCompressionFlags GetCharTypeForCompression( sal_Unicode cChar ) 132 { 133 switch ( cChar ) 134 { 135 case 0x3008: case 0x300A: case 0x300C: case 0x300E: 136 case 0x3010: case 0x3014: case 0x3016: case 0x3018: 137 case 0x301A: case 0x301D: 138 { 139 return AsianCompressionFlags::PunctuationRight; 140 } 141 case 0x3001: case 0x3002: case 0x3009: case 0x300B: 142 case 0x300D: case 0x300F: case 0x3011: case 0x3015: 143 case 0x3017: case 0x3019: case 0x301B: case 0x301E: 144 case 0x301F: 145 { 146 return AsianCompressionFlags::PunctuationLeft; 147 } 148 default: 149 { 150 return ( ( 0x3040 <= cChar ) && ( 0x3100 > cChar ) ) ? AsianCompressionFlags::Kana : AsianCompressionFlags::Normal; 151 } 152 } 153 } 154 155 static void lcl_DrawRedLines( OutputDevice* pOutDev, 156 long nFontHeight, 157 const Point& rPoint, 158 size_t nIndex, 159 size_t nMaxEnd, 160 const long* pDXArray, 161 WrongList const * pWrongs, 162 short nOrientation, 163 const Point& rOrigin, 164 bool bVertical, 165 bool bIsRightToLeft ) 166 { 167 // But only if font is not too small... 168 long nHeight = pOutDev->LogicToPixel(Size(0, nFontHeight)).Height(); 169 if (WRONG_SHOW_MIN >= nHeight) 170 return; 171 172 size_t nEnd, nStart = nIndex; 173 bool bWrong = pWrongs->NextWrong(nStart, nEnd); 174 175 while (bWrong) 176 { 177 if (nStart >= nMaxEnd) 178 break; 179 180 if (nStart < nIndex) // Corrected 181 nStart = nIndex; 182 183 if (nEnd > nMaxEnd) 184 nEnd = nMaxEnd; 185 186 Point aPoint1(rPoint); 187 if (bVertical) 188 { 189 // VCL doesn't know that the text is vertical, and is manipulating 190 // the positions a little bit in y direction... 191 long nOnePixel = pOutDev->PixelToLogic(Size(0, 1)).Height(); 192 long nCorrect = 2 * nOnePixel; 193 aPoint1.AdjustY(-nCorrect); 194 aPoint1.AdjustX(-nCorrect); 195 } 196 if (nStart > nIndex) 197 { 198 if (!bVertical) 199 { 200 // since for RTL portions rPoint is on the visual right end of the portion 201 // (i.e. at the start of the first RTL char) we need to subtract the offset 202 // for RTL portions... 203 aPoint1.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nStart - nIndex - 1]); 204 } 205 else 206 aPoint1.AdjustY(pDXArray[nStart - nIndex - 1]); 207 } 208 Point aPoint2(rPoint); 209 DBG_ASSERT(nEnd > nIndex, "RedLine: aPnt2?"); 210 if (!bVertical) 211 { 212 // since for RTL portions rPoint is on the visual right end of the portion 213 // (i.e. at the start of the first RTL char) we need to subtract the offset 214 // for RTL portions... 215 aPoint2.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nEnd - nIndex - 1]); 216 } 217 else 218 { 219 aPoint2.AdjustY(pDXArray[nEnd - nIndex - 1]); 220 } 221 222 if (nOrientation) 223 { 224 aPoint1 = Rotate(aPoint1, nOrientation, rOrigin); 225 aPoint2 = Rotate(aPoint2, nOrientation, rOrigin); 226 } 227 228 { 229 vcl::ScopedAntialiasing a(*pOutDev, true); 230 pOutDev->DrawWaveLine(aPoint1, aPoint2); 231 } 232 233 nStart = nEnd + 1; 234 if (nEnd < nMaxEnd) 235 bWrong = pWrongs->NextWrong(nStart, nEnd); 236 else 237 bWrong = false; 238 } 239 } 240 241 static Point lcl_ImplCalcRotatedPos( Point rPos, Point rOrigin, double nSin, double nCos ) 242 { 243 Point aRotatedPos; 244 // Translation... 245 Point aTranslatedPos( rPos); 246 aTranslatedPos -= rOrigin; 247 248 aRotatedPos.setX( static_cast<long>( nCos*aTranslatedPos.X() + nSin*aTranslatedPos.Y() ) ); 249 aRotatedPos.setY( static_cast<long>(- ( nSin*aTranslatedPos.X() - nCos*aTranslatedPos.Y() )) ); 250 aTranslatedPos = aRotatedPos; 251 // Translation... 252 aTranslatedPos += rOrigin; 253 254 return aTranslatedPos; 255 } 256 257 static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh ) // For Kashidas from sw/source/core/text/porlay.txt 258 { 259 // Lam + Alef 260 return ( 0x644 == cCh && 0x627 == cNextCh ) || 261 // Beh + Reh 262 ( 0x628 == cCh && 0x631 == cNextCh ); 263 } 264 265 static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh ) // For Kashidas from sw/source/core/text/porlay.txt 266 { 267 // Alef, Dal, Thal, Reh, Zain, and Waw do not connect to the left 268 bool bRet = 0x627 != cPrevCh && 0x62F != cPrevCh && 0x630 != cPrevCh && 269 0x631 != cPrevCh && 0x632 != cPrevCh && 0x648 != cPrevCh; 270 271 // check for ligatures cPrevChar + cChar 272 if ( bRet ) 273 bRet = ! lcl_IsLigature( cPrevCh, cCh ); 274 275 return bRet; 276 } 277 278 279 // class ImpEditEngine 280 281 void ImpEditEngine::UpdateViews( EditView* pCurView ) 282 { 283 if ( !GetUpdateMode() || IsFormatting() || aInvalidRect.IsEmpty() ) 284 return; 285 286 DBG_ASSERT( IsFormatted(), "UpdateViews: Doc not formatted!" ); 287 288 for (EditView* pView : aEditViews) 289 { 290 pView->HideCursor(); 291 292 tools::Rectangle aClipRect( aInvalidRect ); 293 tools::Rectangle aVisArea( pView->GetVisArea() ); 294 aClipRect.Intersection( aVisArea ); 295 296 if ( !aClipRect.IsEmpty() ) 297 { 298 // convert to window coordinates... 299 aClipRect = pView->pImpEditView->GetWindowPos( aClipRect ); 300 301 // moved to one executing method to allow finer control 302 pView->InvalidateWindow(aClipRect); 303 304 pView->InvalidateOtherViewWindows( aClipRect ); 305 } 306 } 307 308 if ( pCurView ) 309 { 310 bool bGotoCursor = pCurView->pImpEditView->DoAutoScroll(); 311 pCurView->ShowCursor( bGotoCursor ); 312 } 313 314 aInvalidRect = tools::Rectangle(); 315 CallStatusHdl(); 316 } 317 318 IMPL_LINK_NOARG(ImpEditEngine, OnlineSpellHdl, Timer *, void) 319 { 320 if ( !Application::AnyInput( VclInputFlags::KEYBOARD ) && GetUpdateMode() && IsFormatted() ) 321 DoOnlineSpelling(); 322 else 323 aOnlineSpellTimer.Start(); 324 } 325 326 IMPL_LINK_NOARG(ImpEditEngine, IdleFormatHdl, Timer *, void) 327 { 328 aIdleFormatter.ResetRestarts(); 329 330 // #i97146# check if that view is still available 331 // else probably the idle format timer fired while we're already 332 // downing 333 EditView* pView = aIdleFormatter.GetView(); 334 for (EditView* aEditView : aEditViews) 335 { 336 if( aEditView == pView ) 337 { 338 FormatAndUpdate( pView ); 339 break; 340 } 341 } 342 } 343 344 void ImpEditEngine::CheckIdleFormatter() 345 { 346 aIdleFormatter.ForceTimeout(); 347 // If not idle, but still not formatted: 348 if ( !IsFormatted() ) 349 FormatDoc(); 350 } 351 352 bool ImpEditEngine::IsPageOverflow( ) const 353 { 354 return mbNeedsChainingHandling; 355 } 356 357 358 void ImpEditEngine::FormatFullDoc() 359 { 360 for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) 361 GetParaPortions()[nPortion]->MarkSelectionInvalid( 0 ); 362 FormatDoc(); 363 } 364 365 void ImpEditEngine::FormatDoc() 366 { 367 if (!GetUpdateMode() || IsFormatting()) 368 return; 369 370 bIsFormatting = true; 371 372 // Then I can also start the spell-timer... 373 if ( GetStatus().DoOnlineSpelling() ) 374 StartOnlineSpellTimer(); 375 376 long nY = 0; 377 bool bGrow = false; 378 379 vcl::Font aOldFont( GetRefDevice()->GetFont() ); 380 381 // Here already, so that not always in CreateLines... 382 bool bMapChanged = ImpCheckRefMapMode(); 383 384 aInvalidRect = tools::Rectangle(); // make empty 385 for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) 386 { 387 ParaPortion* pParaPortion = GetParaPortions()[nPara]; 388 if ( pParaPortion->MustRepaint() || ( pParaPortion->IsInvalid() && pParaPortion->IsVisible() ) ) 389 { 390 // No formatting should be necessary for MustRepaint()! 391 if ( ( pParaPortion->MustRepaint() && !pParaPortion->IsInvalid() ) 392 || CreateLines( nPara, nY ) ) 393 { 394 if ( !bGrow && GetTextRanger() ) 395 { 396 // For a change in height all below must be reformatted... 397 for ( sal_Int32 n = nPara+1; n < GetParaPortions().Count(); n++ ) 398 { 399 ParaPortion* pPP = GetParaPortions()[n]; 400 pPP->MarkSelectionInvalid( 0 ); 401 pPP->GetLines().Reset(); 402 } 403 } 404 bGrow = true; 405 if ( IsCallParaInsertedOrDeleted() ) 406 GetEditEnginePtr()->ParagraphHeightChanged( nPara ); 407 pParaPortion->SetMustRepaint( false ); 408 } 409 410 // InvalidRect set only once... 411 if ( aInvalidRect.IsEmpty() ) 412 { 413 // For Paperwidth 0 (AutoPageSize) it would otherwise be Empty()... 414 long nWidth = std::max( long(1), ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ) ); 415 Range aInvRange( GetInvalidYOffsets( pParaPortion ) ); 416 aInvalidRect = tools::Rectangle( Point( 0, nY+aInvRange.Min() ), 417 Size( nWidth, aInvRange.Len() ) ); 418 } 419 else 420 { 421 aInvalidRect.SetBottom( nY + pParaPortion->GetHeight() ); 422 } 423 } 424 else if ( bGrow ) 425 { 426 aInvalidRect.SetBottom( nY + pParaPortion->GetHeight() ); 427 } 428 nY += pParaPortion->GetHeight(); 429 } 430 431 // One can also get into the formatting through UpdateMode ON=>OFF=>ON... 432 // enable optimization first after Vobis delivery... 433 { 434 sal_uInt32 nNewHeightNTP; 435 sal_uInt32 nNewHeight = CalcTextHeight( &nNewHeightNTP ); 436 long nDiff = nNewHeight - nCurTextHeight; 437 if ( nDiff ) 438 aStatus.GetStatusWord() |= !IsVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED; 439 if ( nNewHeight < nCurTextHeight ) 440 { 441 aInvalidRect.SetBottom( static_cast<long>(std::max( nNewHeight, nCurTextHeight )) ); 442 if ( aInvalidRect.IsEmpty() ) 443 { 444 aInvalidRect.SetTop( 0 ); 445 // Left and Right are not evaluated, are however set due to IsEmpty. 446 aInvalidRect.SetLeft( 0 ); 447 aInvalidRect.SetRight( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ); 448 } 449 } 450 451 nCurTextHeight = nNewHeight; 452 nCurTextHeightNTP = nNewHeightNTP; 453 454 if ( aStatus.AutoPageSize() ) 455 CheckAutoPageSize(); 456 else if ( nDiff ) 457 { 458 for (EditView* pView : aEditViews) 459 { 460 ImpEditView* pImpView = pView->pImpEditView.get(); 461 if ( pImpView->DoAutoHeight() ) 462 { 463 Size aSz( pImpView->GetOutputArea().GetWidth(), nCurTextHeight ); 464 if ( aSz.Height() > aMaxAutoPaperSize.Height() ) 465 aSz.setHeight( aMaxAutoPaperSize.Height() ); 466 else if ( aSz.Height() < aMinAutoPaperSize.Height() ) 467 aSz.setHeight( aMinAutoPaperSize.Height() ); 468 pImpView->ResetOutputArea( tools::Rectangle( 469 pImpView->GetOutputArea().TopLeft(), aSz ) ); 470 } 471 } 472 } 473 } 474 475 bIsFormatting = false; 476 bFormatted = true; 477 478 if ( bMapChanged ) 479 GetRefDevice()->Pop(); 480 481 CallStatusHdl(); // If Modified... 482 } 483 484 bool ImpEditEngine::ImpCheckRefMapMode() 485 { 486 bool bChange = false; 487 488 if ( aStatus.DoFormat100() ) 489 { 490 MapMode aMapMode( GetRefDevice()->GetMapMode() ); 491 if ( aMapMode.GetScaleX().GetNumerator() != aMapMode.GetScaleX().GetDenominator() ) 492 bChange = true; 493 else if ( aMapMode.GetScaleY().GetNumerator() != aMapMode.GetScaleY().GetDenominator() ) 494 bChange = true; 495 496 if ( bChange ) 497 { 498 Fraction Scale1( 1, 1 ); 499 aMapMode.SetScaleX( Scale1 ); 500 aMapMode.SetScaleY( Scale1 ); 501 GetRefDevice()->Push(); 502 GetRefDevice()->SetMapMode( aMapMode ); 503 } 504 } 505 506 return bChange; 507 } 508 509 void ImpEditEngine::CheckAutoPageSize() 510 { 511 Size aPrevPaperSize( GetPaperSize() ); 512 if ( GetStatus().AutoPageWidth() ) 513 aPaperSize.setWidth( !IsVertical() ? CalcTextWidth( true ) : GetTextHeight() ); 514 if ( GetStatus().AutoPageHeight() ) 515 aPaperSize.setHeight( !IsVertical() ? GetTextHeight() : CalcTextWidth( true ) ); 516 517 SetValidPaperSize( aPaperSize ); // consider Min, Max 518 519 if ( aPaperSize != aPrevPaperSize ) 520 { 521 if ( ( !IsVertical() && ( aPaperSize.Width() != aPrevPaperSize.Width() ) ) 522 || ( IsVertical() && ( aPaperSize.Height() != aPrevPaperSize.Height() ) ) ) 523 { 524 // If ahead is centered / right or tabs... 525 aStatus.GetStatusWord() |= !IsVertical() ? EditStatusFlags::TEXTWIDTHCHANGED : EditStatusFlags::TextHeightChanged; 526 for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) 527 { 528 // Only paragraphs which are not aligned to the left need to be 529 // reformatted, the height can not be changed here anymore. 530 ParaPortion* pParaPortion = GetParaPortions()[nPara]; 531 SvxAdjust eJustification = GetJustification( nPara ); 532 if ( eJustification != SvxAdjust::Left ) 533 { 534 pParaPortion->MarkSelectionInvalid( 0 ); 535 CreateLines( nPara, 0 ); // 0: For AutoPageSize no TextRange! 536 } 537 } 538 } 539 540 Size aInvSize = aPaperSize; 541 if ( aPaperSize.Width() < aPrevPaperSize.Width() ) 542 aInvSize.setWidth( aPrevPaperSize.Width() ); 543 if ( aPaperSize.Height() < aPrevPaperSize.Height() ) 544 aInvSize.setHeight( aPrevPaperSize.Height() ); 545 546 Size aSz( aInvSize ); 547 if ( IsVertical() ) 548 { 549 aSz.setWidth( aInvSize.Height() ); 550 aSz.setHeight( aInvSize.Width() ); 551 } 552 aInvalidRect = tools::Rectangle( Point(), aSz ); 553 554 555 for (EditView* pView : aEditViews) 556 { 557 pView->pImpEditView->RecalcOutputArea(); 558 } 559 } 560 } 561 562 void ImpEditEngine::CheckPageOverflow() 563 { 564 SAL_INFO("editeng.chaining", "[CONTROL_STATUS] AutoPageSize is " << (( aStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") ); 565 566 sal_uInt32 nBoxHeight = GetMaxAutoPaperSize().Height(); 567 SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current MaxAutoPaperHeight is " << nBoxHeight); 568 569 sal_uInt32 nTxtHeight = CalcTextHeight(nullptr); 570 SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current Text Height is " << nTxtHeight); 571 572 sal_uInt32 nParaCount = GetParaPortions().Count(); 573 sal_uInt32 nFirstLineCount = GetLineCount(0); 574 bool bOnlyOneEmptyPara = (nParaCount == 1) && 575 (nFirstLineCount == 1) && 576 (GetLineLen(0,0) == 0); 577 578 if (nTxtHeight > nBoxHeight && !bOnlyOneEmptyPara) 579 { 580 // which paragraph is the first to cause higher size of the box? 581 ImplUpdateOverflowingParaNum( nBoxHeight); // XXX: currently only for horizontal text 582 //aStatus.SetPageOverflow(true); 583 mbNeedsChainingHandling = true; 584 } else 585 { 586 // No overflow if within box boundaries 587 //aStatus.SetPageOverflow(false); 588 mbNeedsChainingHandling = false; 589 } 590 591 } 592 593 static sal_Int32 ImplCalculateFontIndependentLineSpacing( const sal_Int32 nFontHeight ) 594 { 595 return ( nFontHeight * 12 ) / 10; // + 20% 596 } 597 598 bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) 599 { 600 ParaPortion* pParaPortion = GetParaPortions()[nPara]; 601 602 // sal_Bool: Changes in the height of paragraph Yes / No - sal_True/sal_False 603 DBG_ASSERT( pParaPortion->GetNode(), "Portion without Node in CreateLines" ); 604 DBG_ASSERT( pParaPortion->IsVisible(), "Invisible paragraphs not formatted!" ); 605 DBG_ASSERT( pParaPortion->IsInvalid(), "CreateLines: Portion not invalid!" ); 606 607 bool bProcessingEmptyLine = ( pParaPortion->GetNode()->Len() == 0 ); 608 bool bEmptyNodeWithPolygon = ( pParaPortion->GetNode()->Len() == 0 ) && GetTextRanger(); 609 610 611 // Fast special treatment for empty paragraphs... 612 613 if ( ( pParaPortion->GetNode()->Len() == 0 ) && !GetTextRanger() ) 614 { 615 // fast special treatment... 616 if ( pParaPortion->GetTextPortions().Count() ) 617 pParaPortion->GetTextPortions().Reset(); 618 if ( pParaPortion->GetLines().Count() ) 619 pParaPortion->GetLines().Reset(); 620 CreateAndInsertEmptyLine( pParaPortion ); 621 return FinishCreateLines( pParaPortion ); 622 } 623 624 625 // Initialization... 626 627 628 // Always format for 100%: 629 bool bMapChanged = ImpCheckRefMapMode(); 630 631 if ( pParaPortion->GetLines().Count() == 0 ) 632 { 633 EditLine* pL = new EditLine; 634 pParaPortion->GetLines().Append(pL); 635 } 636 637 638 // Get Paragraph attributes... 639 640 ContentNode* const pNode = pParaPortion->GetNode(); 641 642 bool bRightToLeftPara = IsRightToLeft( nPara ); 643 644 SvxAdjust eJustification = GetJustification( nPara ); 645 bool bHyphenatePara = pNode->GetContentAttribs().GetItem( EE_PARA_HYPHENATE ).GetValue(); 646 sal_Int32 nSpaceBefore = 0; 647 sal_Int32 nMinLabelWidth = 0; 648 sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pNode, &nSpaceBefore, &nMinLabelWidth ); 649 const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pNode ); 650 const SvxLineSpacingItem& rLSItem = pNode->GetContentAttribs().GetItem( EE_PARA_SBL ); 651 const bool bScriptSpace = pNode->GetContentAttribs().GetItem( EE_PARA_ASIANCJKSPACING ).GetValue(); 652 653 const short nInvalidDiff = pParaPortion->GetInvalidDiff(); 654 const sal_Int32 nInvalidStart = pParaPortion->GetInvalidPosStart(); 655 const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff ); 656 657 bool bQuickFormat = false; 658 if ( !bEmptyNodeWithPolygon && !HasScriptType( nPara, i18n::ScriptType::COMPLEX ) ) 659 { 660 if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff > 0 ) && 661 ( pNode->GetString().indexOf( CH_FEATURE, nInvalidStart ) > nInvalidEnd ) ) 662 { 663 bQuickFormat = true; 664 } 665 else if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff < 0 ) ) 666 { 667 // check if delete over the portion boundaries was done... 668 sal_Int32 nStart = nInvalidStart; // DOUBLE !!!!!!!!!!!!!!! 669 sal_Int32 nEnd = nStart - nInvalidDiff; // negative 670 bQuickFormat = true; 671 sal_Int32 nPos = 0; 672 sal_Int32 nPortions = pParaPortion->GetTextPortions().Count(); 673 for ( sal_Int32 nTP = 0; nTP < nPortions; nTP++ ) 674 { 675 // There must be no start / end in the deleted area. 676 const TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ]; 677 nPos = nPos + rTP.GetLen(); 678 if ( ( nPos > nStart ) && ( nPos < nEnd ) ) 679 { 680 bQuickFormat = false; 681 break; 682 } 683 } 684 } 685 } 686 687 // Saving both layout mode and language (since I'm potentially changing both) 688 GetRefDevice()->Push( PushFlags::TEXTLAYOUTMODE|PushFlags::TEXTLANGUAGE ); 689 690 ImplInitLayoutMode( GetRefDevice(), nPara, -1 ); 691 692 sal_Int32 nRealInvalidStart = nInvalidStart; 693 694 if ( bEmptyNodeWithPolygon ) 695 { 696 TextPortion* pDummyPortion = new TextPortion( 0 ); 697 pParaPortion->GetTextPortions().Reset(); 698 pParaPortion->GetTextPortions().Append(pDummyPortion); 699 } 700 else if ( bQuickFormat ) 701 { 702 // faster Method: 703 RecalcTextPortion( pParaPortion, nInvalidStart, nInvalidDiff ); 704 } 705 else // nRealInvalidStart can be before InvalidStart, since Portions were deleted... 706 { 707 CreateTextPortions( pParaPortion, nRealInvalidStart ); 708 } 709 710 711 // Search for line with InvalidPos, start one line before 712 // Flag the line => do not remove it ! 713 714 715 sal_Int32 nLine = pParaPortion->GetLines().Count()-1; 716 for ( sal_Int32 nL = 0; nL <= nLine; nL++ ) 717 { 718 EditLine& rLine = pParaPortion->GetLines()[nL]; 719 if ( rLine.GetEnd() > nRealInvalidStart ) // not nInvalidStart! 720 { 721 nLine = nL; 722 break; 723 } 724 rLine.SetValid(); 725 } 726 // Begin one line before... 727 // If it is typed at the end, the line in front cannot change. 728 if ( nLine && ( !pParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->Len() ) || ( nInvalidDiff <= 0 ) ) ) 729 nLine--; 730 731 EditLine* pLine = &pParaPortion->GetLines()[nLine]; 732 733 static tools::Rectangle aZeroArea { Point(), Point() }; 734 tools::Rectangle aBulletArea( aZeroArea ); 735 if ( !nLine ) 736 { 737 aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) ); 738 if ( !aBulletArea.IsWidthEmpty() && aBulletArea.Right() > 0 ) 739 pParaPortion->SetBulletX( static_cast<sal_Int32>(GetXValue( aBulletArea.Right() )) ); 740 else 741 pParaPortion->SetBulletX( 0 ); // if Bullet is set incorrectly 742 } 743 744 745 // Reformat all lines from here... 746 747 sal_Int32 nDelFromLine = -1; 748 bool bLineBreak = false; 749 750 sal_Int32 nIndex = pLine->GetStart(); 751 EditLine aSaveLine( *pLine ); 752 SvxFont aTmpFont( pNode->GetCharAttribs().GetDefFont() ); 753 754 ImplInitLayoutMode( GetRefDevice(), nPara, nIndex ); 755 756 std::unique_ptr<long[]> pBuf(new long[ pNode->Len() ]); 757 758 bool bSameLineAgain = false; // For TextRanger, if the height changes. 759 TabInfo aCurrentTab; 760 761 bool bForceOneRun = bEmptyNodeWithPolygon; 762 bool bCompressedChars = false; 763 764 while ( ( nIndex < pNode->Len() ) || bForceOneRun ) 765 { 766 bForceOneRun = false; 767 768 bool bEOL = false; 769 bool bEOC = false; 770 sal_Int32 nPortionStart = 0; 771 sal_Int32 nPortionEnd = 0; 772 773 long nStartX = GetXValue( rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth ); 774 if ( nIndex == 0 ) 775 { 776 long nFI = GetXValue( rLRItem.GetTextFirstLineOfst() ); 777 nStartX += nFI; 778 779 if ( !nLine && ( pParaPortion->GetBulletX() > nStartX ) ) 780 { 781 nStartX = pParaPortion->GetBulletX(); 782 } 783 } 784 785 long nMaxLineWidth; 786 if ( !IsVertical() ) 787 nMaxLineWidth = aStatus.AutoPageWidth() ? aMaxAutoPaperSize.Width() : aPaperSize.Width(); 788 else 789 nMaxLineWidth = aStatus.AutoPageHeight() ? aMaxAutoPaperSize.Height() : aPaperSize.Height(); 790 791 nMaxLineWidth -= GetXValue( rLRItem.GetRight() ); 792 nMaxLineWidth -= nStartX; 793 794 // If PaperSize == long_max, one cannot take away any negative 795 // first line indent. (Overflow) 796 if ( ( nMaxLineWidth < 0 ) && ( nStartX < 0 ) ) 797 nMaxLineWidth = ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ) - GetXValue( rLRItem.GetRight() ); 798 799 // If still less than 0, it may be just the right edge. 800 if ( nMaxLineWidth <= 0 ) 801 nMaxLineWidth = 1; 802 803 // Problem: 804 // Since formatting starts a line _before_ the invalid position, 805 // the positions unfortunately have to be redefined... 806 // Solution: 807 // The line before can only become longer, not smaller 808 // =>... 809 pLine->GetCharPosArray().clear(); 810 811 sal_Int32 nTmpPos = nIndex; 812 sal_Int32 nTmpPortion = pLine->GetStartPortion(); 813 long nTmpWidth = 0; 814 long nXWidth = nMaxLineWidth; 815 816 LongDqPtr pTextRanges = nullptr; 817 long nTextExtraYOffset = 0; 818 long nTextXOffset = 0; 819 long nTextLineHeight = 0; 820 if ( GetTextRanger() ) 821 { 822 GetTextRanger()->SetVertical( IsVertical() ); 823 824 long nTextY = nStartPosY + GetEditCursor( pParaPortion, pLine->GetStart() ).Top(); 825 if ( !bSameLineAgain ) 826 { 827 SeekCursor( pNode, nTmpPos+1, aTmpFont ); 828 aTmpFont.SetPhysFont( GetRefDevice() ); 829 ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage()); 830 831 if ( IsFixedCellHeight() ) 832 nTextLineHeight = ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ); 833 else 834 nTextLineHeight = aTmpFont.GetPhysTxtSize( GetRefDevice() ).Height(); 835 // Metrics can be greater 836 FormatterFontMetric aTempFormatterMetrics; 837 RecalcFormatterFontMetrics( aTempFormatterMetrics, aTmpFont ); 838 sal_uInt16 nLineHeight = aTempFormatterMetrics.GetHeight(); 839 if ( nLineHeight > nTextLineHeight ) 840 nTextLineHeight = nLineHeight; 841 } 842 else 843 nTextLineHeight = pLine->GetHeight(); 844 845 nXWidth = 0; 846 while ( !nXWidth ) 847 { 848 long nYOff = nTextY + nTextExtraYOffset; 849 long nYDiff = nTextLineHeight; 850 if ( IsVertical() ) 851 { 852 long nMaxPolygonX = GetTextRanger()->GetBoundRect().Right(); 853 nYOff = nMaxPolygonX-nYOff; 854 nYDiff = -nTextLineHeight; 855 } 856 pTextRanges = GetTextRanger()->GetTextRanges( Range( nYOff, nYOff + nYDiff ) ); 857 DBG_ASSERT( pTextRanges, "GetTextRanges?!" ); 858 long nMaxRangeWidth = 0; 859 // Use the widest range... 860 // The widest range could be a bit confusing, so normally it 861 // is the first one. Best with gaps. 862 assert(pTextRanges->size() % 2 == 0 && "textranges are always in pairs"); 863 if (!pTextRanges->empty()) 864 { 865 long nA = pTextRanges->at(0); 866 long nB = pTextRanges->at(1); 867 DBG_ASSERT( nA <= nB, "TextRange distorted?" ); 868 long nW = nB - nA; 869 if ( nW > nMaxRangeWidth ) 870 { 871 nMaxRangeWidth = nW; 872 nTextXOffset = nA; 873 } 874 } 875 nXWidth = nMaxRangeWidth; 876 if ( nXWidth ) 877 nMaxLineWidth = nXWidth - nStartX - GetXValue( rLRItem.GetRight() ); 878 else 879 { 880 // Try further down in the polygon. 881 // Below the polygon use the Paper Width. 882 nTextExtraYOffset += std::max( static_cast<long>(nTextLineHeight / 10), long(1) ); 883 if ( ( nTextY + nTextExtraYOffset ) > GetTextRanger()->GetBoundRect().Bottom() ) 884 { 885 nXWidth = !IsVertical() ? GetPaperSize().Width() : GetPaperSize().Height(); 886 if ( !nXWidth ) // AutoPaperSize 887 nXWidth = 0x7FFFFFFF; 888 } 889 } 890 } 891 } 892 893 // search for Portion that no longer fits in line... 894 TextPortion* pPortion = nullptr; 895 sal_Int32 nPortionLen = 0; 896 bool bContinueLastPortion = false; 897 bool bBrokenLine = false; 898 bLineBreak = false; 899 const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature( pLine->GetStart() ); 900 while ( ( nTmpWidth < nXWidth ) && !bEOL ) 901 { 902 const sal_Int32 nTextPortions = pParaPortion->GetTextPortions().Count(); 903 assert(nTextPortions > 0); 904 bContinueLastPortion = (nTmpPortion >= nTextPortions); 905 if (bContinueLastPortion) 906 { 907 if (nTmpPos >= pNode->Len()) 908 break; // while 909 910 // Continue with remainder. This only to have *some* valid 911 // X-values and not endlessly create new lines until DOOM.. 912 // Happened in the scenario of tdf#104152 where inserting a 913 // paragraph lead to a11y attempting to format the doc to 914 // obtain content when notified. 915 nTmpPortion = nTextPortions - 1; 916 SAL_WARN("editeng","ImpEditEngine::CreateLines - continuation of a broken portion"); 917 } 918 919 nPortionStart = nTmpPos; 920 pPortion = &pParaPortion->GetTextPortions()[nTmpPortion]; 921 if ( !bContinueLastPortion && pPortion->GetKind() == PortionKind::HYPHENATOR ) 922 { 923 // Throw away a Portion, if necessary correct the one before, 924 // if the Hyph portion has swallowed a character... 925 sal_Int32 nTmpLen = pPortion->GetLen(); 926 pParaPortion->GetTextPortions().Remove( nTmpPortion ); 927 if (nTmpPortion && nTmpLen) 928 { 929 nTmpPortion--; 930 TextPortion& rPrev = pParaPortion->GetTextPortions()[nTmpPortion]; 931 DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" ); 932 nTmpWidth -= rPrev.GetSize().Width(); 933 nTmpPos = nTmpPos - rPrev.GetLen(); 934 rPrev.SetLen(rPrev.GetLen() + nTmpLen); 935 rPrev.GetSize().setWidth( -1 ); 936 } 937 938 DBG_ASSERT( nTmpPortion < pParaPortion->GetTextPortions().Count(), "No more Portions left!" ); 939 pPortion = &pParaPortion->GetTextPortions()[nTmpPortion]; 940 } 941 942 if (bContinueLastPortion) 943 { 944 // Note that this may point behind the portion and is only to 945 // be used with the node's string offsets to generate X-values. 946 nPortionLen = pNode->Len() - nPortionStart; 947 } 948 else 949 { 950 nPortionLen = pPortion->GetLen(); 951 } 952 953 DBG_ASSERT( pPortion->GetKind() != PortionKind::HYPHENATOR, "CreateLines: Hyphenator-Portion!" ); 954 DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion in CreateLines ?!" ); 955 if ( pNextFeature && ( pNextFeature->GetStart() == nTmpPos ) ) 956 { 957 SAL_WARN_IF( bContinueLastPortion, 958 "editeng","ImpEditEngine::CreateLines - feature in continued portion will be wrong"); 959 sal_uInt16 nWhich = pNextFeature->GetItem()->Which(); 960 switch ( nWhich ) 961 { 962 case EE_FEATURE_TAB: 963 { 964 long nOldTmpWidth = nTmpWidth; 965 966 // Search for Tab-Pos... 967 long nCurPos = nTmpWidth+nStartX; 968 // consider scaling 969 if ( aStatus.DoStretch() && ( nStretchX != 100 ) ) 970 nCurPos = nCurPos*100/std::max(static_cast<sal_Int32>(nStretchX), static_cast<sal_Int32>(1)); 971 972 short nAllSpaceBeforeText = static_cast< short >(rLRItem.GetTextLeft()/* + rLRItem.GetTextLeft()*/ + nSpaceBeforeAndMinLabelWidth); 973 aCurrentTab.aTabStop = pNode->GetContentAttribs().FindTabStop( nCurPos - nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/, aEditDoc.GetDefTab() ); 974 aCurrentTab.nTabPos = GetXValue( static_cast<long>( aCurrentTab.aTabStop.GetTabPos() + nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/ ) ); 975 aCurrentTab.bValid = false; 976 977 // Switch direction in R2L para... 978 if ( bRightToLeftPara ) 979 { 980 if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) 981 aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Left; 982 else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Left ) 983 aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Right; 984 } 985 986 if ( ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) || 987 ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) || 988 ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) ) 989 { 990 // For LEFT / DEFAULT this tab is not considered. 991 aCurrentTab.bValid = true; 992 aCurrentTab.nStartPosX = nTmpWidth; 993 aCurrentTab.nTabPortion = nTmpPortion; 994 } 995 996 pPortion->SetKind(PortionKind::TAB); 997 pPortion->SetExtraValue( aCurrentTab.aTabStop.GetFill() ); 998 pPortion->GetSize().setWidth( aCurrentTab.nTabPos - (nTmpWidth+nStartX) ); 999 1000 // Height needed... 1001 SeekCursor( pNode, nTmpPos+1, aTmpFont ); 1002 pPortion->GetSize().setHeight( aTmpFont.QuickGetTextSize( GetRefDevice(), OUString(), 0, 0 ).Height() ); 1003 1004 DBG_ASSERT( pPortion->GetSize().Width() >= 0, "Tab incorrectly calculated!" ); 1005 1006 nTmpWidth = aCurrentTab.nTabPos-nStartX; 1007 1008 // If this is the first token on the line, 1009 // and nTmpWidth > aPaperSize.Width, => infinite loop! 1010 if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) ) 1011 { 1012 // What now? 1013 // make the tab fitting 1014 pPortion->GetSize().setWidth( nXWidth-nOldTmpWidth ); 1015 nTmpWidth = nXWidth-1; 1016 bEOL = true; 1017 bBrokenLine = true; 1018 } 1019 EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); 1020 size_t nPos = nTmpPos - pLine->GetStart(); 1021 rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width()); 1022 bCompressedChars = false; 1023 } 1024 break; 1025 case EE_FEATURE_LINEBR: 1026 { 1027 DBG_ASSERT( pPortion, "?!" ); 1028 pPortion->GetSize().setWidth( 0 ); 1029 bEOL = true; 1030 bLineBreak = true; 1031 pPortion->SetKind( PortionKind::LINEBREAK ); 1032 bCompressedChars = false; 1033 EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); 1034 size_t nPos = nTmpPos - pLine->GetStart(); 1035 rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width()); 1036 } 1037 break; 1038 case EE_FEATURE_FIELD: 1039 { 1040 SeekCursor( pNode, nTmpPos+1, aTmpFont ); 1041 aTmpFont.SetPhysFont( GetRefDevice() ); 1042 ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage()); 1043 1044 OUString aFieldValue = static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue(); 1045 // get size, but also DXArray to allow length information in line breaking below 1046 const sal_Int32 nLength(aFieldValue.getLength()); 1047 std::unique_ptr<long[]> pTmpDXArray(new long[nLength]); 1048 pPortion->GetSize() = aTmpFont.QuickGetTextSize(GetRefDevice(), aFieldValue, 0, aFieldValue.getLength(), pTmpDXArray.get()); 1049 1050 // So no scrolling for oversized fields 1051 if ( pPortion->GetSize().Width() > nXWidth ) 1052 { 1053 // create ExtraPortionInfo on-demand, flush lineBreaksList 1054 ExtraPortionInfo *pExtraInfo = pPortion->GetExtraInfos(); 1055 1056 if(nullptr == pExtraInfo) 1057 { 1058 pExtraInfo = new ExtraPortionInfo(); 1059 pExtraInfo->nOrgWidth = nXWidth; 1060 pPortion->SetExtraInfos(pExtraInfo); 1061 } 1062 else 1063 { 1064 pExtraInfo->lineBreaksList.clear(); 1065 } 1066 1067 // iterate over CellBreaks using XBreakIterator to be on the 1068 // safe side with international texts/charSets 1069 Reference < i18n::XBreakIterator > xBreakIterator(ImplGetBreakIterator()); 1070 const sal_Int32 nTextLength(aFieldValue.getLength()); 1071 const lang::Locale aLocale(GetLocale(EditPaM(pNode, nPortionStart))); 1072 sal_Int32 nDone(0); 1073 sal_Int32 nNextCellBreak( 1074 xBreakIterator->nextCharacters( 1075 aFieldValue, 1076 0, 1077 aLocale, 1078 css::i18n::CharacterIteratorMode::SKIPCELL, 1079 0, 1080 nDone)); 1081 sal_Int32 nLastCellBreak(0); 1082 sal_Int32 nLineStartX(0); 1083 1084 // always add 1st line break (safe, we already know we are larger than nXWidth) 1085 pExtraInfo->lineBreaksList.push_back(0); 1086 1087 for(sal_Int32 a(0); a < nTextLength; a++) 1088 { 1089 if(a == nNextCellBreak) 1090 { 1091 // check width 1092 if(pTmpDXArray[a] - nLineStartX > nXWidth) 1093 { 1094 // new CellBreak does not fit in current line, need to 1095 // create a break at LastCellBreak - but do not add 1st 1096 // line break twice for very tall frames 1097 if(0 != a) 1098 { 1099 pExtraInfo->lineBreaksList.push_back(a); 1100 } 1101 1102 // moveLineStart forward in X 1103 nLineStartX = pTmpDXArray[nLastCellBreak]; 1104 } 1105 1106 // update CellBreak iteration values 1107 nLastCellBreak = a; 1108 nNextCellBreak = xBreakIterator->nextCharacters( 1109 aFieldValue, 1110 a, 1111 aLocale, 1112 css::i18n::CharacterIteratorMode::SKIPCELL, 1113 1, 1114 nDone); 1115 } 1116 } 1117 } 1118 nTmpWidth += pPortion->GetSize().Width(); 1119 EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); 1120 size_t nPos = nTmpPos - pLine->GetStart(); 1121 rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width()); 1122 pPortion->SetKind(PortionKind::FIELD); 1123 // If this is the first token on the line, 1124 // and nTmpWidth > aPaperSize.Width, => infinite loop! 1125 if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) ) 1126 { 1127 nTmpWidth = nXWidth-1; 1128 bEOL = true; 1129 bBrokenLine = true; 1130 } 1131 // Compression in Fields???? 1132 // I think this could be a little bit difficult and is not very useful 1133 bCompressedChars = false; 1134 } 1135 break; 1136 default: OSL_FAIL( "What feature?" ); 1137 } 1138 pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1 ); 1139 } 1140 else 1141 { 1142 DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion - Extra Space?!" ); 1143 SeekCursor( pNode, nTmpPos+1, aTmpFont ); 1144 aTmpFont.SetPhysFont( GetRefDevice() ); 1145 ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage()); 1146 1147 if (!bContinueLastPortion) 1148 pPortion->SetRightToLeftLevel( GetRightToLeft( nPara, nTmpPos+1 ) ); 1149 1150 if (bContinueLastPortion) 1151 { 1152 Size aSize( aTmpFont.QuickGetTextSize( GetRefDevice(), 1153 pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, pBuf.get() )); 1154 pPortion->GetSize().AdjustWidth(aSize.Width() ); 1155 if (pPortion->GetSize().Height() < aSize.Height()) 1156 pPortion->GetSize().setHeight( aSize.Height() ); 1157 } 1158 else 1159 { 1160 pPortion->GetSize() = aTmpFont.QuickGetTextSize( GetRefDevice(), 1161 pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, pBuf.get() ); 1162 } 1163 1164 // #i9050# Do Kerning also behind portions... 1165 if ( ( aTmpFont.GetFixKerning() > 0 ) && ( ( nTmpPos + nPortionLen ) < pNode->Len() ) ) 1166 pPortion->GetSize().AdjustWidth(aTmpFont.GetFixKerning() ); 1167 if ( IsFixedCellHeight() ) 1168 pPortion->GetSize().setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); 1169 // The array is generally flattened at the beginning 1170 // => Always simply quick inserts. 1171 size_t nPos = nTmpPos - pLine->GetStart(); 1172 EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); 1173 rArray.insert( rArray.begin() + nPos, pBuf.get(), pBuf.get() + nPortionLen); 1174 1175 // And now check for Compression: 1176 if ( !bContinueLastPortion && nPortionLen && GetAsianCompressionMode() != CharCompressType::NONE ) 1177 { 1178 long* pDXArray = rArray.data() + nTmpPos - pLine->GetStart(); 1179 bCompressedChars |= ImplCalcAsianCompression( 1180 pNode, pPortion, nTmpPos, pDXArray, 10000, false); 1181 } 1182 1183 nTmpWidth += pPortion->GetSize().Width(); 1184 1185 sal_Int32 _nPortionEnd = nTmpPos + nPortionLen; 1186 if( bScriptSpace && ( _nPortionEnd < pNode->Len() ) && ( nTmpWidth < nXWidth ) && IsScriptChange( EditPaM( pNode, _nPortionEnd ) ) ) 1187 { 1188 bool bAllow = false; 1189 sal_uInt16 nScriptTypeLeft = GetI18NScriptType( EditPaM( pNode, _nPortionEnd ) ); 1190 sal_uInt16 nScriptTypeRight = GetI18NScriptType( EditPaM( pNode, _nPortionEnd+1 ) ); 1191 if ( ( nScriptTypeLeft == i18n::ScriptType::ASIAN ) || ( nScriptTypeRight == i18n::ScriptType::ASIAN ) ) 1192 bAllow = true; 1193 1194 // No spacing within L2R/R2L nesting 1195 if ( bAllow ) 1196 { 1197 long nExtraSpace = pPortion->GetSize().Height()/5; 1198 nExtraSpace = GetXValue( nExtraSpace ); 1199 pPortion->GetSize().AdjustWidth(nExtraSpace ); 1200 nTmpWidth += nExtraSpace; 1201 } 1202 } 1203 } 1204 1205 if ( aCurrentTab.bValid && ( nTmpPortion != aCurrentTab.nTabPortion ) ) 1206 { 1207 long nWidthAfterTab = 0; 1208 for ( sal_Int32 n = aCurrentTab.nTabPortion+1; n <= nTmpPortion; n++ ) 1209 { 1210 const TextPortion& rTP = pParaPortion->GetTextPortions()[n]; 1211 nWidthAfterTab += rTP.GetSize().Width(); 1212 } 1213 long nW = nWidthAfterTab; // Length before tab position 1214 if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) 1215 { 1216 } 1217 else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) 1218 { 1219 nW = nWidthAfterTab/2; 1220 } 1221 else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) 1222 { 1223 OUString aText = GetSelected( EditSelection( EditPaM( pParaPortion->GetNode(), nTmpPos ), 1224 EditPaM( pParaPortion->GetNode(), nTmpPos + nPortionLen ) ) ); 1225 sal_Int32 nDecPos = aText.indexOf( aCurrentTab.aTabStop.GetDecimal() ); 1226 if ( nDecPos != -1 ) 1227 { 1228 nW -= pParaPortion->GetTextPortions()[nTmpPortion].GetSize().Width(); 1229 nW += aTmpFont.QuickGetTextSize( GetRefDevice(), pParaPortion->GetNode()->GetString(), nTmpPos, nDecPos ).Width(); 1230 aCurrentTab.bValid = false; 1231 } 1232 } 1233 else 1234 { 1235 OSL_FAIL( "CreateLines: Tab not handled!" ); 1236 } 1237 long nMaxW = aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nStartX; 1238 if ( nW >= nMaxW ) 1239 { 1240 nW = nMaxW; 1241 aCurrentTab.bValid = false; 1242 } 1243 TextPortion& rTabPortion = pParaPortion->GetTextPortions()[aCurrentTab.nTabPortion]; 1244 rTabPortion.GetSize().setWidth( aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nW - nStartX ); 1245 nTmpWidth = aCurrentTab.nStartPosX + rTabPortion.GetSize().Width() + nWidthAfterTab; 1246 } 1247 1248 nTmpPos = nTmpPos + nPortionLen; 1249 nPortionEnd = nTmpPos; 1250 nTmpPortion++; 1251 if ( aStatus.OneCharPerLine() ) 1252 bEOL = true; 1253 } 1254 1255 DBG_ASSERT( pPortion, "no portion!?" ); 1256 1257 aCurrentTab.bValid = false; 1258 1259 assert(pLine); 1260 1261 // this was possibly a portion too far: 1262 bool bFixedEnd = false; 1263 if ( aStatus.OneCharPerLine() ) 1264 { 1265 // State before Portion (apart from nTmpWidth): 1266 nTmpPos -= pPortion ? nPortionLen : 0; 1267 nPortionStart = nTmpPos; 1268 nTmpPortion--; 1269 1270 bEOL = true; 1271 bEOC = false; 1272 1273 // And now just one character: 1274 nTmpPos++; 1275 nTmpPortion++; 1276 nPortionEnd = nTmpPortion; 1277 // one Non-Feature-Portion has to be wrapped 1278 if ( pPortion && nPortionLen > 1 ) 1279 { 1280 DBG_ASSERT( pPortion->GetKind() == PortionKind::TEXT, "Len>1, but no TextPortion?" ); 1281 nTmpWidth -= pPortion->GetSize().Width(); 1282 sal_Int32 nP = SplitTextPortion( pParaPortion, nTmpPos, pLine ); 1283 nTmpWidth += pParaPortion->GetTextPortions()[nP].GetSize().Width(); 1284 } 1285 } 1286 else if ( nTmpWidth >= nXWidth ) 1287 { 1288 nPortionEnd = nTmpPos; 1289 nTmpPos -= pPortion ? nPortionLen : 0; 1290 nPortionStart = nTmpPos; 1291 nTmpPortion--; 1292 bEOL = false; 1293 bEOC = false; 1294 if( pPortion ) switch ( pPortion->GetKind() ) 1295 { 1296 case PortionKind::TEXT: 1297 { 1298 nTmpWidth -= pPortion->GetSize().Width(); 1299 } 1300 break; 1301 case PortionKind::FIELD: 1302 case PortionKind::TAB: 1303 { 1304 nTmpWidth -= pPortion->GetSize().Width(); 1305 bEOL = true; 1306 bFixedEnd = true; 1307 } 1308 break; 1309 default: 1310 { 1311 // A feature is not wrapped: 1312 DBG_ASSERT( ( pPortion->GetKind() == PortionKind::LINEBREAK ), "What Feature ?" ); 1313 bEOL = true; 1314 bFixedEnd = true; 1315 } 1316 } 1317 } 1318 else 1319 { 1320 bEOL = true; 1321 bEOC = true; 1322 pLine->SetEnd( nPortionEnd ); 1323 DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No TextPortions?" ); 1324 pLine->SetEndPortion( pParaPortion->GetTextPortions().Count() - 1 ); 1325 } 1326 1327 if ( aStatus.OneCharPerLine() ) 1328 { 1329 pLine->SetEnd( nPortionEnd ); 1330 pLine->SetEndPortion( nTmpPortion-1 ); 1331 } 1332 else if ( bFixedEnd ) 1333 { 1334 pLine->SetEnd( nPortionStart ); 1335 pLine->SetEndPortion( nTmpPortion-1 ); 1336 } 1337 else if ( bLineBreak || bBrokenLine ) 1338 { 1339 pLine->SetEnd( nPortionStart+1 ); 1340 pLine->SetEndPortion( nTmpPortion-1 ); 1341 bEOC = false; // was set above, maybe change the sequence of the if's? 1342 } 1343 else if ( !bEOL && !bContinueLastPortion ) 1344 { 1345 DBG_ASSERT( pPortion && ((nPortionEnd-nPortionStart) == pPortion->GetLen()), "However, another portion?!" ); 1346 long nRemainingWidth = nMaxLineWidth - nTmpWidth; 1347 bool bCanHyphenate = ( aTmpFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL ); 1348 if ( bCompressedChars && pPortion && ( pPortion->GetLen() > 1 ) && pPortion->GetExtraInfos() && pPortion->GetExtraInfos()->bCompressed ) 1349 { 1350 // I need the manipulated DXArray for determining the break position... 1351 long* pDXArray = pLine->GetCharPosArray().data() + (nPortionStart - pLine->GetStart()); 1352 ImplCalcAsianCompression( 1353 pNode, pPortion, nPortionStart, pDXArray, 10000, true); 1354 } 1355 if( pPortion ) 1356 ImpBreakLine( pParaPortion, pLine, pPortion, nPortionStart, 1357 nRemainingWidth, bCanHyphenate && bHyphenatePara ); 1358 } 1359 1360 1361 // Line finished => adjust 1362 1363 1364 // CalcTextSize should be replaced by a continuous registering! 1365 Size aTextSize = pLine->CalcTextSize( *pParaPortion ); 1366 1367 if ( aTextSize.Height() == 0 ) 1368 { 1369 SeekCursor( pNode, pLine->GetStart()+1, aTmpFont ); 1370 aTmpFont.SetPhysFont( pRefDev ); 1371 ImplInitDigitMode(pRefDev, aTmpFont.GetLanguage()); 1372 1373 if ( IsFixedCellHeight() ) 1374 aTextSize.setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); 1375 else 1376 aTextSize.setHeight( aTmpFont.GetPhysTxtSize( pRefDev ).Height() ); 1377 pLine->SetHeight( static_cast<sal_uInt16>(aTextSize.Height()) ); 1378 } 1379 1380 // The font metrics can not be calculated continuously, if the font is 1381 // set anyway, because a large font only after wrapping suddenly ends 1382 // up in the next line => Font metrics too big. 1383 FormatterFontMetric aFormatterMetrics; 1384 sal_Int32 nTPos = pLine->GetStart(); 1385 for ( sal_Int32 nP = pLine->GetStartPortion(); nP <= pLine->GetEndPortion(); nP++ ) 1386 { 1387 const TextPortion& rTP = pParaPortion->GetTextPortions()[nP]; 1388 // problem with hard font height attribute, when everything but the line break has this attribute 1389 if ( rTP.GetKind() != PortionKind::LINEBREAK ) 1390 { 1391 SeekCursor( pNode, nTPos+1, aTmpFont ); 1392 aTmpFont.SetPhysFont( GetRefDevice() ); 1393 ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage()); 1394 RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont ); 1395 } 1396 nTPos = nTPos + rTP.GetLen(); 1397 } 1398 sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight(); 1399 if ( nLineHeight > pLine->GetHeight() ) 1400 pLine->SetHeight( nLineHeight ); 1401 pLine->SetMaxAscent( aFormatterMetrics.nMaxAscent ); 1402 1403 bSameLineAgain = false; 1404 if ( GetTextRanger() && ( pLine->GetHeight() > nTextLineHeight ) ) 1405 { 1406 // put down with the other size! 1407 bSameLineAgain = true; 1408 } 1409 1410 1411 if ( !bSameLineAgain && !aStatus.IsOutliner() ) 1412 { 1413 if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min ) 1414 { 1415 sal_uInt16 nMinHeight = GetYValue( rLSItem.GetLineHeight() ); 1416 sal_uInt16 nTxtHeight = pLine->GetHeight(); 1417 if ( nTxtHeight < nMinHeight ) 1418 { 1419 // The Ascent has to be adjusted for the difference: 1420 long nDiff = nMinHeight - nTxtHeight; 1421 pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + nDiff) ); 1422 pLine->SetHeight( nMinHeight, nTxtHeight ); 1423 } 1424 } 1425 else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix ) 1426 { 1427 sal_uInt16 nFixHeight = GetYValue( rLSItem.GetLineHeight() ); 1428 sal_uInt16 nTxtHeight = pLine->GetHeight(); 1429 pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) ); 1430 pLine->SetHeight( nFixHeight, nTxtHeight ); 1431 } 1432 else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) 1433 { 1434 // There are documents with PropLineSpace 0, why? 1435 // (cmc: re above question :-) such documents can be seen by importing a .ppt 1436 if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() < 100 ) ) 1437 { 1438 // Adapted code from sw/source/core/text/itrform2.cxx 1439 sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace(); 1440 sal_uInt16 nAscent = pLine->GetMaxAscent(); 1441 sal_uInt16 nNewAscent = pLine->GetTxtHeight() * nPropLineSpace / 100 * 4 / 5; // 80% 1442 if ( !nAscent || nAscent > nNewAscent ) 1443 { 1444 pLine->SetMaxAscent( nNewAscent ); 1445 } 1446 sal_uInt16 nHeight = pLine->GetHeight() * nPropLineSpace / 100; 1447 pLine->SetHeight( nHeight, pLine->GetTxtHeight() ); 1448 } 1449 else if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) ) 1450 { 1451 sal_uInt16 nTxtHeight = pLine->GetHeight(); 1452 sal_Int32 nPropTextHeight = nTxtHeight * rLSItem.GetPropLineSpace() / 100; 1453 // The Ascent has to be adjusted for the difference: 1454 long nDiff = pLine->GetHeight() - nPropTextHeight; 1455 pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() - nDiff ) ); 1456 pLine->SetHeight( static_cast<sal_uInt16>( nPropTextHeight ), nTxtHeight ); 1457 } 1458 } 1459 } 1460 1461 if ( ( !IsVertical() && aStatus.AutoPageWidth() ) || 1462 ( IsVertical() && aStatus.AutoPageHeight() ) ) 1463 { 1464 // If the row fits within the current paper width, then this width 1465 // has to be used for the Alignment. If it does not fit or if it 1466 // will change the paper width, it will be formatted again for 1467 // Justification! = LEFT anyway. 1468 long nMaxLineWidthFix = ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ) 1469 - GetXValue( rLRItem.GetRight() ) - nStartX; 1470 if ( aTextSize.Width() < nMaxLineWidthFix ) 1471 nMaxLineWidth = nMaxLineWidthFix; 1472 } 1473 1474 if ( bCompressedChars ) 1475 { 1476 long nRemainingWidth = nMaxLineWidth - aTextSize.Width(); 1477 if ( nRemainingWidth > 0 ) 1478 { 1479 ImplExpandCompressedPortions( pLine, pParaPortion, nRemainingWidth ); 1480 aTextSize = pLine->CalcTextSize( *pParaPortion ); 1481 } 1482 } 1483 1484 if ( pLine->IsHangingPunctuation() ) 1485 { 1486 // Width from HangingPunctuation was set to 0 in ImpBreakLine, 1487 // check for rel width now, maybe create compression... 1488 long n = nMaxLineWidth - aTextSize.Width(); 1489 TextPortion& rTP = pParaPortion->GetTextPortions()[pLine->GetEndPortion()]; 1490 sal_Int32 nPosInArray = pLine->GetEnd()-1-pLine->GetStart(); 1491 long nNewValue = ( nPosInArray ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ) + n; 1492 pLine->GetCharPosArray()[ nPosInArray ] = nNewValue; 1493 rTP.GetSize().AdjustWidth(n ); 1494 } 1495 1496 pLine->SetTextWidth( aTextSize.Width() ); 1497 switch ( eJustification ) 1498 { 1499 case SvxAdjust::Center: 1500 { 1501 long n = ( nMaxLineWidth - aTextSize.Width() ) / 2; 1502 n += nStartX; // Indentation is kept. 1503 pLine->SetStartPosX( n ); 1504 } 1505 break; 1506 case SvxAdjust::Right: 1507 { 1508 // For automatically wrapped lines, which has a blank at the end 1509 // the blank must not be displayed! 1510 long n = nMaxLineWidth - aTextSize.Width(); 1511 n += nStartX; // Indentation is kept. 1512 pLine->SetStartPosX( n ); 1513 } 1514 break; 1515 case SvxAdjust::Block: 1516 { 1517 bool bDistLastLine = (GetJustifyMethod(nPara) == SvxCellJustifyMethod::Distribute); 1518 long nRemainingSpace = nMaxLineWidth - aTextSize.Width(); 1519 pLine->SetStartPosX( nStartX ); 1520 if ( nRemainingSpace > 0 && (!bEOC || bDistLastLine) ) 1521 ImpAdjustBlocks( pParaPortion, pLine, nRemainingSpace ); 1522 } 1523 break; 1524 default: 1525 { 1526 pLine->SetStartPosX( nStartX ); // FI, LI 1527 } 1528 break; 1529 } 1530 1531 1532 // Check whether the line must be re-issued... 1533 1534 pLine->SetInvalid(); 1535 1536 // If a portion was wrapped there may be far too many positions in 1537 // CharPosArray: 1538 EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); 1539 size_t nLen = pLine->GetLen(); 1540 if (rArray.size() > nLen) 1541 rArray.erase(rArray.begin()+nLen, rArray.end()); 1542 1543 if ( GetTextRanger() ) 1544 { 1545 if ( nTextXOffset ) 1546 pLine->SetStartPosX( pLine->GetStartPosX() + nTextXOffset ); 1547 if ( nTextExtraYOffset ) 1548 { 1549 pLine->SetHeight( static_cast<sal_uInt16>( pLine->GetHeight() + nTextExtraYOffset ), 0 ); 1550 pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() + nTextExtraYOffset ) ); 1551 } 1552 } 1553 1554 // for <0 think over ! 1555 if ( pParaPortion->IsSimpleInvalid() ) 1556 { 1557 // Change through simple Text changes... 1558 // Do not cancel formatting since Portions possibly have to be split 1559 // again! If at some point cancelable, then validate the following 1560 // line! But if applicable, mark as valid, so there is less output... 1561 if ( pLine->GetEnd() < nInvalidStart ) 1562 { 1563 if ( *pLine == aSaveLine ) 1564 { 1565 pLine->SetValid(); 1566 } 1567 } 1568 else 1569 { 1570 sal_Int32 nStart = pLine->GetStart(); 1571 sal_Int32 nEnd = pLine->GetEnd(); 1572 1573 if ( nStart > nInvalidEnd ) 1574 { 1575 if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) && 1576 ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) ) 1577 { 1578 pLine->SetValid(); 1579 if (bQuickFormat) 1580 { 1581 bLineBreak = false; 1582 pParaPortion->CorrectValuesBehindLastFormattedLine( nLine ); 1583 break; 1584 } 1585 } 1586 } 1587 else if (bQuickFormat && (nEnd > nInvalidEnd)) 1588 { 1589 // If the invalid line ends so that the next begins on the 1590 // 'same' passage as before, i.e. not wrapped differently, 1591 // then the text width does not have to be determined anew: 1592 if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) ) 1593 { 1594 bLineBreak = false; 1595 pParaPortion->CorrectValuesBehindLastFormattedLine( nLine ); 1596 break; 1597 } 1598 } 1599 } 1600 } 1601 1602 if ( !bSameLineAgain ) 1603 { 1604 nIndex = pLine->GetEnd(); // next line start = last line end 1605 // as nEnd points to the last character! 1606 1607 sal_Int32 nEndPortion = pLine->GetEndPortion(); 1608 1609 // Next line or maybe a new line... 1610 pLine = nullptr; 1611 if ( nLine < pParaPortion->GetLines().Count()-1 ) 1612 pLine = &pParaPortion->GetLines()[++nLine]; 1613 if ( pLine && ( nIndex >= pNode->Len() ) ) 1614 { 1615 nDelFromLine = nLine; 1616 break; 1617 } 1618 if ( !pLine ) 1619 { 1620 if ( nIndex < pNode->Len() ) 1621 { 1622 pLine = new EditLine; 1623 pParaPortion->GetLines().Insert(++nLine, pLine); 1624 } 1625 else if ( nIndex && bLineBreak && GetTextRanger() ) 1626 { 1627 // normally CreateAndInsertEmptyLine would be called, but I want to use 1628 // CreateLines, so I need Polygon code only here... 1629 TextPortion* pDummyPortion = new TextPortion( 0 ); 1630 pParaPortion->GetTextPortions().Append(pDummyPortion); 1631 pLine = new EditLine; 1632 pParaPortion->GetLines().Insert(++nLine, pLine); 1633 bForceOneRun = true; 1634 bProcessingEmptyLine = true; 1635 } 1636 } 1637 if ( pLine ) 1638 { 1639 aSaveLine = *pLine; 1640 pLine->SetStart( nIndex ); 1641 pLine->SetEnd( nIndex ); 1642 pLine->SetStartPortion( nEndPortion+1 ); 1643 pLine->SetEndPortion( nEndPortion+1 ); 1644 } 1645 } 1646 } // while ( Index < Len ) 1647 1648 if ( nDelFromLine >= 0 ) 1649 pParaPortion->GetLines().DeleteFromLine( nDelFromLine ); 1650 1651 DBG_ASSERT( pParaPortion->GetLines().Count(), "No line after CreateLines!" ); 1652 1653 if ( bLineBreak ) 1654 CreateAndInsertEmptyLine( pParaPortion ); 1655 1656 pBuf.reset(); 1657 1658 bool bHeightChanged = FinishCreateLines( pParaPortion ); 1659 1660 if ( bMapChanged ) 1661 GetRefDevice()->Pop(); 1662 1663 GetRefDevice()->Pop(); 1664 1665 return bHeightChanged; 1666 } 1667 1668 void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion ) 1669 { 1670 DBG_ASSERT( !GetTextRanger(), "Don't use CreateAndInsertEmptyLine with a polygon!" ); 1671 1672 EditLine* pTmpLine = new EditLine; 1673 pTmpLine->SetStart( pParaPortion->GetNode()->Len() ); 1674 pTmpLine->SetEnd( pParaPortion->GetNode()->Len() ); 1675 pParaPortion->GetLines().Append(pTmpLine); 1676 1677 bool bLineBreak = pParaPortion->GetNode()->Len() > 0; 1678 sal_Int32 nSpaceBefore = 0; 1679 sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pParaPortion->GetNode(), &nSpaceBefore ); 1680 const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pParaPortion->GetNode() ); 1681 const SvxLineSpacingItem& rLSItem = pParaPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); 1682 long nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOfst() + nSpaceBefore ); 1683 1684 tools::Rectangle aBulletArea { Point(), Point() }; 1685 if ( bLineBreak ) 1686 { 1687 nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOfst() + nSpaceBeforeAndMinLabelWidth ); 1688 } 1689 else 1690 { 1691 aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) ); 1692 if ( !aBulletArea.IsEmpty() && aBulletArea.Right() > 0 ) 1693 pParaPortion->SetBulletX( static_cast<sal_Int32>(GetXValue( aBulletArea.Right() )) ); 1694 else 1695 pParaPortion->SetBulletX( 0 ); // If Bullet set incorrectly. 1696 if ( pParaPortion->GetBulletX() > nStartX ) 1697 { 1698 nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOfst() + nSpaceBeforeAndMinLabelWidth ); 1699 if ( pParaPortion->GetBulletX() > nStartX ) 1700 nStartX = pParaPortion->GetBulletX(); 1701 } 1702 } 1703 1704 SvxFont aTmpFont; 1705 SeekCursor( pParaPortion->GetNode(), bLineBreak ? pParaPortion->GetNode()->Len() : 0, aTmpFont ); 1706 aTmpFont.SetPhysFont( pRefDev ); 1707 1708 TextPortion* pDummyPortion = new TextPortion( 0 ); 1709 pDummyPortion->GetSize() = aTmpFont.GetPhysTxtSize( pRefDev ); 1710 if ( IsFixedCellHeight() ) 1711 pDummyPortion->GetSize().setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); 1712 pParaPortion->GetTextPortions().Append(pDummyPortion); 1713 FormatterFontMetric aFormatterMetrics; 1714 RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont ); 1715 pTmpLine->SetMaxAscent( aFormatterMetrics.nMaxAscent ); 1716 pTmpLine->SetHeight( static_cast<sal_uInt16>(pDummyPortion->GetSize().Height()) ); 1717 sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight(); 1718 if ( nLineHeight > pTmpLine->GetHeight() ) 1719 pTmpLine->SetHeight( nLineHeight ); 1720 1721 if ( !aStatus.IsOutliner() ) 1722 { 1723 sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion ); 1724 SvxAdjust eJustification = GetJustification( nPara ); 1725 long nMaxLineWidth = !IsVertical() ? aPaperSize.Width() : aPaperSize.Height(); 1726 nMaxLineWidth -= GetXValue( rLRItem.GetRight() ); 1727 if ( nMaxLineWidth < 0 ) 1728 nMaxLineWidth = 1; 1729 if ( eJustification == SvxAdjust::Center ) 1730 nStartX = nMaxLineWidth / 2; 1731 else if ( eJustification == SvxAdjust::Right ) 1732 nStartX = nMaxLineWidth; 1733 } 1734 1735 pTmpLine->SetStartPosX( nStartX ); 1736 1737 if ( !aStatus.IsOutliner() ) 1738 { 1739 if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min ) 1740 { 1741 sal_uInt16 nMinHeight = rLSItem.GetLineHeight(); 1742 sal_uInt16 nTxtHeight = pTmpLine->GetHeight(); 1743 if ( nTxtHeight < nMinHeight ) 1744 { 1745 // The Ascent has to be adjusted for the difference: 1746 long nDiff = nMinHeight - nTxtHeight; 1747 pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff) ); 1748 pTmpLine->SetHeight( nMinHeight, nTxtHeight ); 1749 } 1750 } 1751 else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix ) 1752 { 1753 sal_uInt16 nFixHeight = rLSItem.GetLineHeight(); 1754 sal_uInt16 nTxtHeight = pTmpLine->GetHeight(); 1755 1756 pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) ); 1757 pTmpLine->SetHeight( nFixHeight, nTxtHeight ); 1758 } 1759 else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) 1760 { 1761 sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion ); 1762 if ( nPara || pTmpLine->GetStartPortion() ) // Not the very first line 1763 { 1764 // There are documents with PropLineSpace 0, why? 1765 // (cmc: re above question :-) such documents can be seen by importing a .ppt 1766 if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) ) 1767 { 1768 sal_uInt16 nTxtHeight = pTmpLine->GetHeight(); 1769 sal_Int32 nH = nTxtHeight; 1770 nH *= rLSItem.GetPropLineSpace(); 1771 nH /= 100; 1772 // The Ascent has to be adjusted for the difference: 1773 long nDiff = pTmpLine->GetHeight() - nH; 1774 if ( nDiff > pTmpLine->GetMaxAscent() ) 1775 nDiff = pTmpLine->GetMaxAscent(); 1776 pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() - nDiff) ); 1777 pTmpLine->SetHeight( static_cast<sal_uInt16>(nH), nTxtHeight ); 1778 } 1779 } 1780 } 1781 } 1782 1783 if ( !bLineBreak ) 1784 { 1785 long nMinHeight = aBulletArea.GetHeight(); 1786 if ( nMinHeight > static_cast<long>(pTmpLine->GetHeight()) ) 1787 { 1788 long nDiff = nMinHeight - static_cast<long>(pTmpLine->GetHeight()); 1789 // distribute nDiff upwards and downwards 1790 pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff/2) ); 1791 pTmpLine->SetHeight( static_cast<sal_uInt16>(nMinHeight) ); 1792 } 1793 } 1794 else 1795 { 1796 // -2: The new one is already inserted. 1797 #ifdef DBG_UTIL 1798 EditLine& rLastLine = pParaPortion->GetLines()[pParaPortion->GetLines().Count()-2]; 1799 DBG_ASSERT( rLastLine.GetEnd() == pParaPortion->GetNode()->Len(), "different anyway?" ); 1800 #endif 1801 sal_Int32 nPos = pParaPortion->GetTextPortions().Count() - 1 ; 1802 pTmpLine->SetStartPortion( nPos ); 1803 pTmpLine->SetEndPortion( nPos ); 1804 } 1805 } 1806 1807 bool ImpEditEngine::FinishCreateLines( ParaPortion* pParaPortion ) 1808 { 1809 // CalcCharPositions( pParaPortion ); 1810 pParaPortion->SetValid(); 1811 long nOldHeight = pParaPortion->GetHeight(); 1812 CalcHeight( pParaPortion ); 1813 1814 DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "FinishCreateLines: No Text-Portion?" ); 1815 bool bRet = ( pParaPortion->GetHeight() != nOldHeight ); 1816 return bRet; 1817 } 1818 1819 void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, TextPortion const * pPortion, sal_Int32 nPortionStart, long nRemainingWidth, bool bCanHyphenate ) 1820 { 1821 ContentNode* const pNode = pParaPortion->GetNode(); 1822 1823 sal_Int32 nBreakInLine = nPortionStart - pLine->GetStart(); 1824 sal_Int32 nMax = nBreakInLine + pPortion->GetLen(); 1825 while ( ( nBreakInLine < nMax ) && ( pLine->GetCharPosArray()[nBreakInLine] < nRemainingWidth ) ) 1826 nBreakInLine++; 1827 1828 sal_Int32 nMaxBreakPos = nBreakInLine + pLine->GetStart(); 1829 sal_Int32 nBreakPos = SAL_MAX_INT32; 1830 1831 bool bCompressBlank = false; 1832 bool bHyphenated = false; 1833 bool bHangingPunctuation = false; 1834 sal_Unicode cAlternateReplChar = 0; 1835 sal_Unicode cAlternateExtraChar = 0; 1836 bool bAltFullLeft = false; 1837 bool bAltFullRight = false; 1838 sal_uInt32 nAltDelChar = 0; 1839 1840 if ( ( nMaxBreakPos < ( nMax + pLine->GetStart() ) ) && ( pNode->GetChar( nMaxBreakPos ) == ' ' ) ) 1841 { 1842 // Break behind the blank, blank will be compressed... 1843 nBreakPos = nMaxBreakPos + 1; 1844 bCompressBlank = true; 1845 } 1846 else 1847 { 1848 sal_Int32 nMinBreakPos = pLine->GetStart(); 1849 const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); 1850 for (size_t nAttr = rAttrs.size(); nAttr; ) 1851 { 1852 const EditCharAttrib& rAttr = *rAttrs[--nAttr].get(); 1853 if (rAttr.IsFeature() && rAttr.GetEnd() > nMinBreakPos && rAttr.GetEnd() <= nMaxBreakPos) 1854 { 1855 nMinBreakPos = rAttr.GetEnd(); 1856 break; 1857 } 1858 } 1859 assert(nMinBreakPos <= nMaxBreakPos); 1860 1861 lang::Locale aLocale = GetLocale( EditPaM( pNode, nMaxBreakPos ) ); 1862 1863 Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 1864 const bool bAllowPunctuationOutsideMargin = static_cast<const SfxBoolItem&>( 1865 pNode->GetContentAttribs().GetItem( EE_PARA_HANGINGPUNCTUATION )).GetValue(); 1866 1867 if (nMinBreakPos == nMaxBreakPos) 1868 { 1869 nBreakPos = nMinBreakPos; 1870 } 1871 else 1872 { 1873 Reference< XHyphenator > xHyph; 1874 if ( bCanHyphenate ) 1875 xHyph = GetHyphenator(); 1876 i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, Sequence< PropertyValue >(), 1 ); 1877 i18n::LineBreakUserOptions aUserOptions; 1878 1879 const i18n::ForbiddenCharacters* pForbidden = GetForbiddenCharsTable()->GetForbiddenCharacters( LanguageTag::convertToLanguageType( aLocale ), true ); 1880 aUserOptions.forbiddenBeginCharacters = pForbidden->beginLine; 1881 aUserOptions.forbiddenEndCharacters = pForbidden->endLine; 1882 aUserOptions.applyForbiddenRules = static_cast<const SfxBoolItem&>(pNode->GetContentAttribs().GetItem( EE_PARA_FORBIDDENRULES )).GetValue(); 1883 aUserOptions.allowPunctuationOutsideMargin = bAllowPunctuationOutsideMargin; 1884 aUserOptions.allowHyphenateEnglish = false; 1885 1886 i18n::LineBreakResults aLBR = _xBI->getLineBreak( 1887 pNode->GetString(), nMaxBreakPos, aLocale, nMinBreakPos, aHyphOptions, aUserOptions ); 1888 nBreakPos = aLBR.breakIndex; 1889 1890 // BUG in I18N - under special condition (break behind field, #87327#) breakIndex is < nMinBreakPos 1891 if ( nBreakPos < nMinBreakPos ) 1892 { 1893 nBreakPos = nMinBreakPos; 1894 } 1895 else if ( ( nBreakPos > nMaxBreakPos ) && !aUserOptions.allowPunctuationOutsideMargin ) 1896 { 1897 OSL_FAIL( "I18N: XBreakIterator::getLineBreak returns position > Max" ); 1898 nBreakPos = nMaxBreakPos; 1899 } 1900 1901 // nBreakPos can never be outside the portion, even not with hanging punctuation 1902 if ( nBreakPos > nMaxBreakPos ) 1903 nBreakPos = nMaxBreakPos; 1904 } 1905 1906 // BUG in I18N - the japanese dot is in the next line! 1907 // !!! Test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1908 if ( (nBreakPos + ( bAllowPunctuationOutsideMargin ? 0 : 1 ) ) <= nMaxBreakPos ) 1909 { 1910 sal_Unicode cFirstInNextLine = ( (nBreakPos+1) < pNode->Len() ) ? pNode->GetChar( nBreakPos ) : 0; 1911 if ( cFirstInNextLine == 12290 ) 1912 nBreakPos++; 1913 } 1914 1915 bHangingPunctuation = nBreakPos > nMaxBreakPos; 1916 pLine->SetHangingPunctuation( bHangingPunctuation ); 1917 1918 // Whether a separator or not, push the word after the separator through 1919 // hyphenation... NMaxBreakPos is the last character that fits into 1920 // the line, nBreakPos is the beginning of the word. 1921 // There is a problem if the Doc is so narrow that a word is broken 1922 // into more than two lines... 1923 if ( !bHangingPunctuation && bCanHyphenate && GetHyphenator().is() ) 1924 { 1925 i18n::Boundary aBoundary = _xBI->getWordBoundary( 1926 pNode->GetString(), nBreakPos, GetLocale( EditPaM( pNode, nBreakPos ) ), css::i18n::WordType::DICTIONARY_WORD, true); 1927 sal_Int32 nWordStart = nBreakPos; 1928 sal_Int32 nWordEnd = aBoundary.endPos; 1929 DBG_ASSERT( nWordEnd >= nWordStart, "Start >= End?" ); 1930 1931 sal_Int32 nWordLen = nWordEnd - nWordStart; 1932 if ( ( nWordEnd >= nMaxBreakPos ) && ( nWordLen > 3 ) ) 1933 { 1934 // May happen, because getLineBreak may differ from getWordBoudary with DICTIONARY_WORD 1935 const OUString aWord = pNode->GetString().copy(nWordStart, nWordLen); 1936 sal_Int32 nMinTrail = nWordEnd-nMaxBreakPos+1; //+1: Before the dickey letter 1937 Reference< XHyphenatedWord > xHyphWord; 1938 if (xHyphenator.is()) 1939 xHyphWord = xHyphenator->hyphenate( aWord, aLocale, aWord.getLength() - nMinTrail, Sequence< PropertyValue >() ); 1940 if (xHyphWord.is()) 1941 { 1942 bool bAlternate = xHyphWord->isAlternativeSpelling(); 1943 sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos(); 1944 1945 if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= (pLine->GetStart() + 2 ) ) ) 1946 { 1947 if ( !bAlternate ) 1948 { 1949 bHyphenated = true; 1950 nBreakPos = nWordStart + _nWordLen; 1951 } 1952 else 1953 { 1954 // TODO: handle all alternative hyphenations (see hyphen-1.2.8/tests/unicode.*) 1955 OUString aAlt( xHyphWord->getHyphenatedWord() ); 1956 OUString aAltLeft(aAlt.copy(0, _nWordLen)); 1957 OUString aAltRight(aAlt.copy(_nWordLen)); 1958 bAltFullLeft = aWord.startsWith(aAltLeft); 1959 bAltFullRight = aWord.endsWith(aAltRight); 1960 nAltDelChar = aWord.getLength() - aAlt.getLength() + static_cast<int>(!bAltFullLeft) + static_cast<int>(!bAltFullRight); 1961 1962 // NOTE: improved for other cases, see fdo#63711 1963 1964 // We expect[ed] the two cases: 1965 // 1) packen becomes pak-ken 1966 // 2) Schiffahrt becomes Schiff-fahrt 1967 // In case 1, a character has to be replaced 1968 // in case 2 a character is added. 1969 // The identification is complicated by long 1970 // compound words because the Hyphenator separates 1971 // all position of the word. [This is not true for libhyphen.] 1972 // "Schiffahrtsbrennesseln" -> "Schifffahrtsbrennnesseln" 1973 // We can thus actually not directly connect the index of the 1974 // AlternativeWord to aWord. The whole issue will be simplified 1975 // by a function in the Hyphenator as soon as AMA builds this in... 1976 sal_Int32 nAltStart = _nWordLen - 1; 1977 sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength()); 1978 sal_Int32 nTxtEnd = nTxtStart; 1979 sal_Int32 nAltEnd = nAltStart; 1980 1981 // The regions between the nStart and nEnd is the 1982 // difference between alternative and original string. 1983 while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() && 1984 aWord[nTxtEnd] != aAlt[nAltEnd] ) 1985 { 1986 ++nTxtEnd; 1987 ++nAltEnd; 1988 } 1989 1990 // If a character is added, then we notice it now: 1991 if( nAltEnd > nTxtEnd && nAltStart == nAltEnd && 1992 aWord[ nTxtEnd ] == aAlt[nAltEnd] ) 1993 { 1994 ++nAltEnd; 1995 ++nTxtStart; 1996 ++nTxtEnd; 1997 } 1998 1999 DBG_ASSERT( ( nAltEnd - nAltStart ) == 1, "Alternate: Wrong assumption!" ); 2000 2001 if ( nTxtEnd > nTxtStart ) 2002 cAlternateReplChar = aAlt[nAltStart]; 2003 else 2004 cAlternateExtraChar = aAlt[nAltStart]; 2005 2006 bHyphenated = true; 2007 nBreakPos = nWordStart + nTxtStart; 2008 if ( cAlternateReplChar || aAlt.getLength() < aWord.getLength() || !bAltFullRight) // also for "oma-tje", "re-eel" 2009 nBreakPos++; 2010 } 2011 } 2012 } 2013 } 2014 } 2015 2016 if ( nBreakPos <= pLine->GetStart() ) 2017 { 2018 // No separator in line => Chop! 2019 nBreakPos = nMaxBreakPos; 2020 // I18N nextCharacters ! 2021 if ( nBreakPos <= pLine->GetStart() ) 2022 nBreakPos = pLine->GetStart() + 1; // Otherwise infinite loop! 2023 } 2024 } 2025 2026 // the dickey portion is the end portion 2027 pLine->SetEnd( nBreakPos ); 2028 2029 sal_Int32 nEndPortion = SplitTextPortion( pParaPortion, nBreakPos, pLine ); 2030 2031 if ( !bCompressBlank && !bHangingPunctuation ) 2032 { 2033 // When justification is not SvxAdjust::Left, it's important to compress 2034 // the trailing space even if there is enough room for the space... 2035 // Don't check for SvxAdjust::Left, doesn't matter to compress in this case too... 2036 DBG_ASSERT( nBreakPos > pLine->GetStart(), "ImpBreakLines - BreakPos not expected!" ); 2037 if ( pNode->GetChar( nBreakPos-1 ) == ' ' ) 2038 bCompressBlank = true; 2039 } 2040 2041 if ( bCompressBlank || bHangingPunctuation ) 2042 { 2043 TextPortion& rTP = pParaPortion->GetTextPortions()[nEndPortion]; 2044 DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "BlankRubber: No TextPortion!" ); 2045 DBG_ASSERT( nBreakPos > pLine->GetStart(), "SplitTextPortion at the beginning of the line?" ); 2046 sal_Int32 nPosInArray = nBreakPos - 1 - pLine->GetStart(); 2047 rTP.GetSize().setWidth( ( nPosInArray && ( rTP.GetLen() > 1 ) ) ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ); 2048 pLine->GetCharPosArray()[ nPosInArray ] = rTP.GetSize().Width(); 2049 } 2050 else if ( bHyphenated ) 2051 { 2052 // A portion for inserting the separator... 2053 TextPortion* pHyphPortion = new TextPortion( 0 ); 2054 pHyphPortion->SetKind( PortionKind::HYPHENATOR ); 2055 OUString aHyphText(CH_HYPH); 2056 if ( (cAlternateReplChar || cAlternateExtraChar) && bAltFullRight ) // alternation after the break doesn't supported 2057 { 2058 TextPortion& rPrev = pParaPortion->GetTextPortions()[nEndPortion]; 2059 DBG_ASSERT( rPrev.GetLen(), "Hyphenate: Prev portion?!" ); 2060 rPrev.SetLen( rPrev.GetLen() - nAltDelChar ); 2061 pHyphPortion->SetLen( nAltDelChar ); 2062 if (cAlternateReplChar && !bAltFullLeft) pHyphPortion->SetExtraValue( cAlternateReplChar ); 2063 // Correct width of the portion above: 2064 rPrev.GetSize().setWidth( 2065 pLine->GetCharPosArray()[ nBreakPos-1 - pLine->GetStart() - nAltDelChar ] ); 2066 } 2067 2068 // Determine the width of the Hyph-Portion: 2069 SvxFont aFont; 2070 SeekCursor( pParaPortion->GetNode(), nBreakPos, aFont ); 2071 aFont.SetPhysFont( GetRefDevice() ); 2072 pHyphPortion->GetSize().setHeight( GetRefDevice()->GetTextHeight() ); 2073 pHyphPortion->GetSize().setWidth( GetRefDevice()->GetTextWidth( aHyphText ) ); 2074 2075 pParaPortion->GetTextPortions().Insert(++nEndPortion, pHyphPortion); 2076 } 2077 pLine->SetEndPortion( nEndPortion ); 2078 } 2079 2080 void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, long nRemainingSpace ) 2081 { 2082 DBG_ASSERT( nRemainingSpace > 0, "AdjustBlocks: Somewhat too little..." ); 2083 DBG_ASSERT( pLine, "AdjustBlocks: Line ?!" ); 2084 if ( ( nRemainingSpace < 0 ) || pLine->IsEmpty() ) 2085 return ; 2086 2087 const sal_Int32 nFirstChar = pLine->GetStart(); 2088 const sal_Int32 nLastChar = pLine->GetEnd() -1; // Last points behind 2089 ContentNode* pNode = pParaPortion->GetNode(); 2090 2091 DBG_ASSERT( nLastChar < pNode->Len(), "AdjustBlocks: Out of range!" ); 2092 2093 // Search blanks or Kashidas... 2094 std::vector<sal_Int32> aPositions; 2095 sal_uInt16 nLastScript = i18n::ScriptType::LATIN; 2096 for ( sal_Int32 nChar = nFirstChar; nChar <= nLastChar; nChar++ ) 2097 { 2098 EditPaM aPaM( pNode, nChar+1 ); 2099 LanguageType eLang = GetLanguage(aPaM); 2100 sal_uInt16 nScript = GetI18NScriptType(aPaM); 2101 if ( MsLangId::getPrimaryLanguage( eLang) == LANGUAGE_ARABIC_PRIMARY_ONLY ) 2102 // Arabic script is handled later. 2103 continue; 2104 2105 if ( pNode->GetChar(nChar) == ' ' ) 2106 { 2107 // Normal latin script. 2108 aPositions.push_back( nChar ); 2109 } 2110 else if (nChar > nFirstChar) 2111 { 2112 if (nLastScript == i18n::ScriptType::ASIAN) 2113 { 2114 // Set break position between this and the last character if 2115 // the last character is asian script. 2116 aPositions.push_back( nChar-1 ); 2117 } 2118 else if (nScript == i18n::ScriptType::ASIAN) 2119 { 2120 // Set break position between a latin script and asian script. 2121 aPositions.push_back( nChar-1 ); 2122 } 2123 } 2124 2125 nLastScript = nScript; 2126 } 2127 2128 // Kashidas ? 2129 ImpFindKashidas( pNode, nFirstChar, nLastChar, aPositions ); 2130 2131 if ( aPositions.empty() ) 2132 return; 2133 2134 // If the last character is a blank, it is rejected! 2135 // The width must be distributed to the blockers in front... 2136 // But not if it is the only one. 2137 if ( ( pNode->GetChar( nLastChar ) == ' ' ) && ( aPositions.size() > 1 ) && 2138 ( MsLangId::getPrimaryLanguage( GetLanguage( EditPaM( pNode, nLastChar ) ) ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) ) 2139 { 2140 aPositions.pop_back(); 2141 sal_Int32 nPortionStart, nPortion; 2142 nPortion = pParaPortion->GetTextPortions().FindPortion( nLastChar+1, nPortionStart ); 2143 TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ]; 2144 long nRealWidth = pLine->GetCharPosArray()[nLastChar-nFirstChar]; 2145 long nBlankWidth = nRealWidth; 2146 if ( nLastChar > nPortionStart ) 2147 nBlankWidth -= pLine->GetCharPosArray()[nLastChar-nFirstChar-1]; 2148 // Possibly the blank has already been deducted in ImpBreakLine: 2149 if ( nRealWidth == rLastPortion.GetSize().Width() ) 2150 { 2151 // For the last character the portion must stop behind the blank 2152 // => Simplify correction: 2153 DBG_ASSERT( ( nPortionStart + rLastPortion.GetLen() ) == ( nLastChar+1 ), "Blank actually not at the end of the portion!?"); 2154 rLastPortion.GetSize().AdjustWidth( -nBlankWidth ); 2155 nRemainingSpace += nBlankWidth; 2156 } 2157 pLine->GetCharPosArray()[nLastChar-nFirstChar] -= nBlankWidth; 2158 } 2159 2160 size_t nGaps = aPositions.size(); 2161 const long nMore4Everyone = nRemainingSpace / nGaps; 2162 long nSomeExtraSpace = nRemainingSpace - nMore4Everyone*nGaps; 2163 2164 DBG_ASSERT( nSomeExtraSpace < static_cast<long>(nGaps), "AdjustBlocks: ExtraSpace too large" ); 2165 DBG_ASSERT( nSomeExtraSpace >= 0, "AdjustBlocks: ExtraSpace < 0 " ); 2166 2167 // Correct the positions in the Array and the portion widths: 2168 // Last character won't be considered... 2169 for (auto const& nChar : aPositions) 2170 { 2171 if ( nChar < nLastChar ) 2172 { 2173 sal_Int32 nPortionStart, nPortion; 2174 nPortion = pParaPortion->GetTextPortions().FindPortion( nChar, nPortionStart, true ); 2175 TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ]; 2176 2177 // The width of the portion: 2178 rLastPortion.GetSize().AdjustWidth(nMore4Everyone ); 2179 if ( nSomeExtraSpace ) 2180 rLastPortion.GetSize().AdjustWidth( 1 ); 2181 2182 // Correct positions in array 2183 // Even for kashidas just change positions, VCL will then draw the kashida automatically 2184 sal_Int32 nPortionEnd = nPortionStart + rLastPortion.GetLen(); 2185 for ( sal_Int32 _n = nChar; _n < nPortionEnd; _n++ ) 2186 { 2187 pLine->GetCharPosArray()[_n-nFirstChar] += nMore4Everyone; 2188 if ( nSomeExtraSpace ) 2189 pLine->GetCharPosArray()[_n-nFirstChar]++; 2190 } 2191 2192 if ( nSomeExtraSpace ) 2193 nSomeExtraSpace--; 2194 } 2195 } 2196 2197 // Now the text width contains the extra width... 2198 pLine->SetTextWidth( pLine->GetTextWidth() + nRemainingSpace ); 2199 } 2200 2201 void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, std::vector<sal_Int32>& rArray ) 2202 { 2203 // the search has to be performed on a per word base 2204 2205 EditSelection aWordSel( EditPaM( pNode, nStart ) ); 2206 aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD ); 2207 if ( aWordSel.Min().GetIndex() < nStart ) 2208 aWordSel.Min().SetIndex( nStart ); 2209 2210 while ( ( aWordSel.Min().GetNode() == pNode ) && ( aWordSel.Min().GetIndex() < nEnd ) ) 2211 { 2212 const sal_Int32 nSavPos = aWordSel.Max().GetIndex(); 2213 if ( aWordSel.Max().GetIndex() > nEnd ) 2214 aWordSel.Max().SetIndex( nEnd ); 2215 2216 OUString aWord = GetSelected( aWordSel ); 2217 2218 // restore selection for proper iteration at the end of the function 2219 aWordSel.Max().SetIndex( nSavPos ); 2220 2221 sal_Int32 nIdx = 0; 2222 sal_Int32 nKashidaPos = -1; 2223 sal_Unicode cCh; 2224 sal_Unicode cPrevCh = 0; 2225 2226 while ( nIdx < aWord.getLength() ) 2227 { 2228 cCh = aWord[ nIdx ]; 2229 2230 // 1. Priority: 2231 // after user inserted kashida 2232 if ( 0x640 == cCh ) 2233 { 2234 nKashidaPos = aWordSel.Min().GetIndex() + nIdx; 2235 break; 2236 } 2237 2238 // 2. Priority: 2239 // after a Seen or Sad 2240 if ( nIdx + 1 < aWord.getLength() && 2241 ( 0x633 == cCh || 0x635 == cCh ) ) 2242 { 2243 nKashidaPos = aWordSel.Min().GetIndex() + nIdx; 2244 break; 2245 } 2246 2247 // 3. Priority: 2248 // before final form of the Marbuta, Hah, Dal 2249 // 4. Priority: 2250 // before final form of Alef, Lam or Kaf 2251 if ( nIdx && nIdx + 1 == aWord.getLength() && 2252 ( 0x629 == cCh || 0x62D == cCh || 0x62F == cCh || 2253 0x627 == cCh || 0x644 == cCh || 0x643 == cCh ) ) 2254 { 2255 DBG_ASSERT( 0 != cPrevCh, "No previous character" ); 2256 2257 // check if character is connectable to previous character, 2258 if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) 2259 { 2260 nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1; 2261 break; 2262 } 2263 } 2264 2265 // 5. Priority: 2266 // before media Bah 2267 if ( nIdx && nIdx + 1 < aWord.getLength() && 0x628 == cCh ) 2268 { 2269 DBG_ASSERT( 0 != cPrevCh, "No previous character" ); 2270 2271 // check if next character is Reh, Yeh or Alef Maksura 2272 sal_Unicode cNextCh = aWord[ nIdx + 1 ]; 2273 2274 if ( 0x631 == cNextCh || 0x64A == cNextCh || 2275 0x649 == cNextCh ) 2276 { 2277 // check if character is connectable to previous character, 2278 if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) 2279 nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1; 2280 } 2281 } 2282 2283 // 6. Priority: 2284 // other connecting possibilities 2285 if ( nIdx && nIdx + 1 == aWord.getLength() && 2286 0x60C <= cCh && 0x6FE >= cCh ) 2287 { 2288 DBG_ASSERT( 0 != cPrevCh, "No previous character" ); 2289 2290 // check if character is connectable to previous character, 2291 if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) 2292 { 2293 // only choose this position if we did not find 2294 // a better one: 2295 if ( nKashidaPos<0 ) 2296 nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1; 2297 break; 2298 } 2299 } 2300 2301 // Do not consider Fathatan, Dammatan, Kasratan, Fatha, 2302 // Damma, Kasra, Shadda and Sukun when checking if 2303 // a character can be connected to previous character. 2304 if ( cCh < 0x64B || cCh > 0x652 ) 2305 cPrevCh = cCh; 2306 2307 ++nIdx; 2308 } // end of current word 2309 2310 if ( nKashidaPos>=0 ) 2311 rArray.push_back( nKashidaPos ); 2312 2313 aWordSel = WordRight( aWordSel.Max(), css::i18n::WordType::DICTIONARY_WORD ); 2314 aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD ); 2315 } 2316 } 2317 2318 sal_Int32 ImpEditEngine::SplitTextPortion( ParaPortion* pPortion, sal_Int32 nPos, EditLine* pCurLine ) 2319 { 2320 DBG_ASSERT( pPortion, "SplitTextPortion: Which ?" ); 2321 2322 // The portion at nPos is split, if there is not a transition at nPos anyway 2323 if ( nPos == 0 ) 2324 return 0; 2325 2326 sal_Int32 nSplitPortion; 2327 sal_Int32 nTmpPos = 0; 2328 TextPortion* pTextPortion = nullptr; 2329 sal_Int32 nPortions = pPortion->GetTextPortions().Count(); 2330 for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ ) 2331 { 2332 TextPortion& rTP = pPortion->GetTextPortions()[nSplitPortion]; 2333 nTmpPos = nTmpPos + rTP.GetLen(); 2334 if ( nTmpPos >= nPos ) 2335 { 2336 if ( nTmpPos == nPos ) // then nothing needs to be split 2337 { 2338 return nSplitPortion; 2339 } 2340 pTextPortion = &rTP; 2341 break; 2342 } 2343 } 2344 2345 DBG_ASSERT( pTextPortion, "Position outside the area!" ); 2346 2347 if (!pTextPortion) 2348 return 0; 2349 2350 DBG_ASSERT( pTextPortion->GetKind() == PortionKind::TEXT, "SplitTextPortion: No TextPortion!" ); 2351 2352 sal_Int32 nOverlapp = nTmpPos - nPos; 2353 pTextPortion->SetLen( pTextPortion->GetLen() - nOverlapp ); 2354 TextPortion* pNewPortion = new TextPortion( nOverlapp ); 2355 pPortion->GetTextPortions().Insert(nSplitPortion+1, pNewPortion); 2356 // Set sizes 2357 if ( pCurLine ) 2358 { 2359 // No new GetTextSize, instead use values from the Array: 2360 DBG_ASSERT( nPos > pCurLine->GetStart(), "SplitTextPortion at the beginning of the line?" ); 2361 pTextPortion->GetSize().setWidth( pCurLine->GetCharPosArray()[ nPos-pCurLine->GetStart()-1 ] ); 2362 2363 if ( pTextPortion->GetExtraInfos() && pTextPortion->GetExtraInfos()->bCompressed ) 2364 { 2365 // We need the original size from the portion 2366 sal_Int32 nTxtPortionStart = pPortion->GetTextPortions().GetStartPos( nSplitPortion ); 2367 SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() ); 2368 SeekCursor( pPortion->GetNode(), nTxtPortionStart+1, aTmpFont ); 2369 aTmpFont.SetPhysFont( GetRefDevice() ); 2370 GetRefDevice()->Push( PushFlags::TEXTLANGUAGE ); 2371 ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage()); 2372 Size aSz = aTmpFont.QuickGetTextSize( GetRefDevice(), pPortion->GetNode()->GetString(), nTxtPortionStart, pTextPortion->GetLen() ); 2373 GetRefDevice()->Pop(); 2374 pTextPortion->GetExtraInfos()->nOrgWidth = aSz.Width(); 2375 } 2376 } 2377 else 2378 pTextPortion->GetSize().setWidth( -1 ); 2379 2380 return nSplitPortion; 2381 } 2382 2383 void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rStart ) 2384 { 2385 sal_Int32 nStartPos = rStart; 2386 ContentNode* pNode = pParaPortion->GetNode(); 2387 DBG_ASSERT( pNode->Len(), "CreateTextPortions should not be used for empty paragraphs!" ); 2388 2389 std::set< sal_Int32 > aPositions; 2390 aPositions.insert( 0 ); 2391 2392 sal_uInt16 nAttr = 0; 2393 EditCharAttrib* pAttrib = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); 2394 while ( pAttrib ) 2395 { 2396 // Insert Start and End into the Array... 2397 // The Insert method does not allow for duplicate values... 2398 aPositions.insert( pAttrib->GetStart() ); 2399 aPositions.insert( pAttrib->GetEnd() ); 2400 nAttr++; 2401 pAttrib = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); 2402 } 2403 aPositions.insert( pNode->Len() ); 2404 2405 if ( pParaPortion->aScriptInfos.empty() ) 2406 InitScriptTypes( GetParaPortions().GetPos( pParaPortion ) ); 2407 2408 const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; 2409 for (const ScriptTypePosInfo& rType : rTypes) 2410 aPositions.insert( rType.nStartPos ); 2411 2412 const WritingDirectionInfos& rWritingDirections = pParaPortion->aWritingDirectionInfos; 2413 for (const WritingDirectionInfo & rWritingDirection : rWritingDirections) 2414 aPositions.insert( rWritingDirection.nStartPos ); 2415 2416 if ( mpIMEInfos && mpIMEInfos->nLen && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) ) 2417 { 2418 ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xFFFF); 2419 for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ ) 2420 { 2421 if ( mpIMEInfos->pAttribs[n] != nLastAttr ) 2422 { 2423 aPositions.insert( mpIMEInfos->aPos.GetIndex() + n ); 2424 nLastAttr = mpIMEInfos->pAttribs[n]; 2425 } 2426 } 2427 aPositions.insert( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ); 2428 } 2429 2430 // From ... Delete: 2431 // Unfortunately, the number of text portions does not have to match 2432 // aPositions.Count(), since there might be line breaks... 2433 sal_Int32 nPortionStart = 0; 2434 sal_Int32 nInvPortion = 0; 2435 sal_Int32 nP; 2436 for ( nP = 0; nP < pParaPortion->GetTextPortions().Count(); nP++ ) 2437 { 2438 const TextPortion& rTmpPortion = pParaPortion->GetTextPortions()[nP]; 2439 nPortionStart = nPortionStart + rTmpPortion.GetLen(); 2440 if ( nPortionStart >= nStartPos ) 2441 { 2442 nPortionStart = nPortionStart - rTmpPortion.GetLen(); 2443 rStart = nPortionStart; 2444 nInvPortion = nP; 2445 break; 2446 } 2447 } 2448 DBG_ASSERT( nP < pParaPortion->GetTextPortions().Count() || !pParaPortion->GetTextPortions().Count(), "Nothing to delete: CreateTextPortions" ); 2449 if ( nInvPortion && ( nPortionStart+pParaPortion->GetTextPortions()[nInvPortion].GetLen() > nStartPos ) ) 2450 { 2451 // prefer one in front... 2452 // But only if it was in the middle of the portion of, otherwise it 2453 // might be the only one in the row in front! 2454 nInvPortion--; 2455 nPortionStart = nPortionStart - pParaPortion->GetTextPortions()[nInvPortion].GetLen(); 2456 } 2457 pParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion ); 2458 2459 // A portion may also have been formed by a line break: 2460 aPositions.insert( nPortionStart ); 2461 2462 std::set< sal_Int32 >::iterator nInvPos = aPositions.find( nPortionStart ); 2463 DBG_ASSERT( (nInvPos != aPositions.end()), "InvPos ?!" ); 2464 2465 std::set< sal_Int32 >::iterator i = nInvPos; 2466 ++i; 2467 while ( i != aPositions.end() ) 2468 { 2469 TextPortion* pNew = new TextPortion( (*i++) - *nInvPos++ ); 2470 pParaPortion->GetTextPortions().Append(pNew); 2471 } 2472 2473 DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions?!" ); 2474 #if OSL_DEBUG_LEVEL > 0 2475 OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portion is broken?" ); 2476 #endif 2477 } 2478 2479 void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars ) 2480 { 2481 DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions!" ); 2482 DBG_ASSERT( nNewChars, "RecalcTextPortion with Diff == 0" ); 2483 2484 ContentNode* const pNode = pParaPortion->GetNode(); 2485 if ( nNewChars > 0 ) 2486 { 2487 // If an Attribute begins/ends at nStartPos, then a new portion starts 2488 // otherwise the portion is extended at nStartPos. 2489 if ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) || IsScriptChange( EditPaM( pNode, nStartPos ) ) ) 2490 { 2491 sal_Int32 nNewPortionPos = 0; 2492 if ( nStartPos ) 2493 nNewPortionPos = SplitTextPortion( pParaPortion, nStartPos ) + 1; 2494 2495 // A blank portion may be here, if the paragraph was empty, 2496 // or if a line was created by a hard line break. 2497 if ( ( nNewPortionPos < pParaPortion->GetTextPortions().Count() ) && 2498 !pParaPortion->GetTextPortions()[nNewPortionPos].GetLen() ) 2499 { 2500 TextPortion& rTP = pParaPortion->GetTextPortions()[nNewPortionPos]; 2501 DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "the empty portion was no TextPortion!" ); 2502 rTP.SetLen( rTP.GetLen() + nNewChars ); 2503 } 2504 else 2505 { 2506 TextPortion* pNewPortion = new TextPortion( nNewChars ); 2507 pParaPortion->GetTextPortions().Insert(nNewPortionPos, pNewPortion); 2508 } 2509 } 2510 else 2511 { 2512 sal_Int32 nPortionStart; 2513 const sal_Int32 nTP = pParaPortion->GetTextPortions(). 2514 FindPortion( nStartPos, nPortionStart ); 2515 TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ]; 2516 rTP.SetLen( rTP.GetLen() + nNewChars ); 2517 rTP.GetSize().setWidth( -1 ); 2518 } 2519 } 2520 else 2521 { 2522 // Shrink or remove portion if necessary. 2523 // Before calling this method it must be ensured that no portions were 2524 // in the deleted area! 2525 2526 // There must be no portions extending into the area or portions starting in 2527 // the area, so it must be: 2528 // nStartPos <= nPos <= nStartPos - nNewChars(neg.) 2529 sal_Int32 nPortion = 0; 2530 sal_Int32 nPos = 0; 2531 sal_Int32 nEnd = nStartPos-nNewChars; 2532 sal_Int32 nPortions = pParaPortion->GetTextPortions().Count(); 2533 TextPortion* pTP = nullptr; 2534 for ( nPortion = 0; nPortion < nPortions; nPortion++ ) 2535 { 2536 pTP = &pParaPortion->GetTextPortions()[ nPortion ]; 2537 if ( ( nPos+pTP->GetLen() ) > nStartPos ) 2538 { 2539 DBG_ASSERT( nPos <= nStartPos, "Wrong Start!" ); 2540 DBG_ASSERT( nPos+pTP->GetLen() >= nEnd, "Wrong End!" ); 2541 break; 2542 } 2543 nPos = nPos + pTP->GetLen(); 2544 } 2545 DBG_ASSERT( pTP, "RecalcTextPortion: Portion not found" ); 2546 if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) ) 2547 { 2548 // Remove portion; 2549 PortionKind nType = pTP->GetKind(); 2550 pParaPortion->GetTextPortions().Remove( nPortion ); 2551 if ( nType == PortionKind::LINEBREAK ) 2552 { 2553 TextPortion& rNext = pParaPortion->GetTextPortions()[ nPortion ]; 2554 if ( !rNext.GetLen() ) 2555 { 2556 // Remove dummy portion 2557 pParaPortion->GetTextPortions().Remove( nPortion ); 2558 } 2559 } 2560 } 2561 else 2562 { 2563 DBG_ASSERT( pTP->GetLen() > (-nNewChars), "Portion too small to shrink! "); 2564 pTP->SetLen( pTP->GetLen() + nNewChars ); 2565 } 2566 2567 sal_Int32 nPortionCount = pParaPortion->GetTextPortions().Count(); 2568 assert( nPortionCount ); 2569 if (nPortionCount) 2570 { 2571 // No HYPHENATOR portion is allowed to get stuck right at the end... 2572 sal_Int32 nLastPortion = nPortionCount - 1; 2573 pTP = &pParaPortion->GetTextPortions()[nLastPortion]; 2574 if ( pTP->GetKind() == PortionKind::HYPHENATOR ) 2575 { 2576 // Discard portion; if possible, correct the ones before, 2577 // if the Hyphenator portion has swallowed one character... 2578 if ( nLastPortion && pTP->GetLen() ) 2579 { 2580 TextPortion& rPrev = pParaPortion->GetTextPortions()[nLastPortion - 1]; 2581 DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" ); 2582 rPrev.SetLen( rPrev.GetLen() + pTP->GetLen() ); 2583 rPrev.GetSize().setWidth( -1 ); 2584 } 2585 pParaPortion->GetTextPortions().Remove( nLastPortion ); 2586 } 2587 } 2588 } 2589 #if OSL_DEBUG_LEVEL > 0 2590 OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portions are broken?" ); 2591 #endif 2592 } 2593 2594 void ImpEditEngine::SetTextRanger( std::unique_ptr<TextRanger> pRanger ) 2595 { 2596 pTextRanger = std::move(pRanger); 2597 2598 for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) 2599 { 2600 ParaPortion* pParaPortion = GetParaPortions()[nPara]; 2601 pParaPortion->MarkSelectionInvalid( 0 ); 2602 pParaPortion->GetLines().Reset(); 2603 } 2604 2605 FormatFullDoc(); 2606 UpdateViews( GetActiveView() ); 2607 if ( GetUpdateMode() && GetActiveView() ) 2608 pActiveView->ShowCursor(false, false); 2609 } 2610 2611 void ImpEditEngine::SetVertical( bool bVertical, bool bTopToBottom) 2612 { 2613 if ( IsVertical() != bVertical || IsTopToBottom() != (bVertical && bTopToBottom)) 2614 { 2615 GetEditDoc().SetVertical( bVertical, bTopToBottom); 2616 bool bUseCharAttribs = bool(aStatus.GetControlWord() & EEControlBits::USECHARATTRIBS); 2617 GetEditDoc().CreateDefFont( bUseCharAttribs ); 2618 if ( IsFormatted() ) 2619 { 2620 FormatFullDoc(); 2621 UpdateViews( GetActiveView() ); 2622 } 2623 } 2624 } 2625 2626 void ImpEditEngine::SetFixedCellHeight( bool bUseFixedCellHeight ) 2627 { 2628 if ( IsFixedCellHeight() != bUseFixedCellHeight ) 2629 { 2630 GetEditDoc().SetFixedCellHeight( bUseFixedCellHeight ); 2631 if ( IsFormatted() ) 2632 { 2633 FormatFullDoc(); 2634 UpdateViews( GetActiveView() ); 2635 } 2636 } 2637 } 2638 2639 void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont, OutputDevice* pOut ) 2640 { 2641 // It was planned, SeekCursor( nStartPos, nEndPos,... ), so that it would 2642 // only be searched anew at the StartPosition. 2643 // Problem: There would be two lists to consider/handle: 2644 // OrderedByStart,OrderedByEnd. 2645 2646 if ( nPos > pNode->Len() ) 2647 nPos = pNode->Len(); 2648 2649 rFont = pNode->GetCharAttribs().GetDefFont(); 2650 2651 /* 2652 * Set attributes for script types Asian and Complex 2653 */ 2654 short nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nPos ) ); 2655 SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N); 2656 if ( ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) || ( nScriptTypeI18N == i18n::ScriptType::COMPLEX ) ) 2657 { 2658 const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) )); 2659 rFont.SetFamilyName( rFontItem.GetFamilyName() ); 2660 rFont.SetFamily( rFontItem.GetFamily() ); 2661 rFont.SetPitch( rFontItem.GetPitch() ); 2662 rFont.SetCharSet( rFontItem.GetCharSet() ); 2663 Size aSz( rFont.GetFontSize() ); 2664 aSz.setHeight( static_cast<const SvxFontHeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) ).GetHeight() ); 2665 rFont.SetFontSize( aSz ); 2666 rFont.SetWeight( static_cast<const SvxWeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ))).GetWeight() ); 2667 rFont.SetItalic( static_cast<const SvxPostureItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ))).GetPosture() ); 2668 rFont.SetLanguage( static_cast<const SvxLanguageItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ))).GetLanguage() ); 2669 } 2670 2671 sal_uInt16 nRelWidth = pNode->GetContentAttribs().GetItem( EE_CHAR_FONTWIDTH).GetValue(); 2672 2673 /* 2674 * Set output device's line and overline colors 2675 */ 2676 if ( pOut ) 2677 { 2678 const SvxUnderlineItem& rTextLineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_UNDERLINE ); 2679 if ( rTextLineColor.GetColor() != COL_TRANSPARENT ) 2680 pOut->SetTextLineColor( rTextLineColor.GetColor() ); 2681 else 2682 pOut->SetTextLineColor(); 2683 2684 const SvxOverlineItem& rOverlineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_OVERLINE ); 2685 if ( rOverlineColor.GetColor() != COL_TRANSPARENT ) 2686 pOut->SetOverlineColor( rOverlineColor.GetColor() ); 2687 else 2688 pOut->SetOverlineColor(); 2689 } 2690 2691 const SvxLanguageItem* pCJKLanguageItem = nullptr; 2692 2693 /* 2694 * Scan through char attributes of pNode 2695 */ 2696 if ( aStatus.UseCharAttribs() ) 2697 { 2698 CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); 2699 size_t nAttr = 0; 2700 EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr); 2701 while ( pAttrib && ( pAttrib->GetStart() <= nPos ) ) 2702 { 2703 // when seeking, ignore attributes which start there! Empty attributes 2704 // are considered (used) as these are just set. But do not use empty 2705 // attributes: When just set and empty => no effect on font 2706 // In a blank paragraph, set characters take effect immediately. 2707 if ( ( pAttrib->Which() != 0 ) && 2708 ( ( ( pAttrib->GetStart() < nPos ) && ( pAttrib->GetEnd() >= nPos ) ) 2709 || ( !pNode->Len() ) ) ) 2710 { 2711 DBG_ASSERT( ( pAttrib->Which() >= EE_CHAR_START ) && ( pAttrib->Which() <= EE_FEATURE_END ), "Invalid Attribute in Seek() " ); 2712 if ( IsScriptItemValid( pAttrib->Which(), nScriptTypeI18N ) ) 2713 { 2714 pAttrib->SetFont( rFont, pOut ); 2715 // #i1550# hard color attrib should win over text color from field 2716 if ( pAttrib->Which() == EE_FEATURE_FIELD ) 2717 { 2718 EditCharAttrib* pColorAttr = pNode->GetCharAttribs().FindAttrib( EE_CHAR_COLOR, nPos ); 2719 if ( pColorAttr ) 2720 pColorAttr->SetFont( rFont, pOut ); 2721 } 2722 } 2723 if ( pAttrib->Which() == EE_CHAR_FONTWIDTH ) 2724 nRelWidth = static_cast<const SvxCharScaleWidthItem*>(pAttrib->GetItem())->GetValue(); 2725 if ( pAttrib->Which() == EE_CHAR_LANGUAGE_CJK ) 2726 pCJKLanguageItem = static_cast<const SvxLanguageItem*>( pAttrib->GetItem() ); 2727 } 2728 pAttrib = GetAttrib( rAttribs, ++nAttr ); 2729 } 2730 } 2731 2732 if ( !pCJKLanguageItem ) 2733 pCJKLanguageItem = &pNode->GetContentAttribs().GetItem( EE_CHAR_LANGUAGE_CJK ); 2734 2735 rFont.SetCJKContextLanguage( pCJKLanguageItem->GetLanguage() ); 2736 2737 if ( (rFont.GetKerning() != FontKerning::NONE) && IsKernAsianPunctuation() && ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) ) 2738 rFont.SetKerning( rFont.GetKerning() | FontKerning::Asian ); 2739 2740 if ( aStatus.DoNotUseColors() ) 2741 { 2742 rFont.SetColor( /* rColorItem.GetValue() */ COL_BLACK ); 2743 } 2744 2745 if ( aStatus.DoStretch() || ( nRelWidth != 100 ) ) 2746 { 2747 // For the current Output device, because otherwise if RefDev=Printer its looks 2748 // ugly on the screen! 2749 OutputDevice* pDev = pOut ? pOut : GetRefDevice(); 2750 rFont.SetPhysFont( pDev ); 2751 FontMetric aMetric( pDev->GetFontMetric() ); 2752 2753 // Set the font as we want it to look like & reset the Propr attribute 2754 // so that it is not counted twice. 2755 Size aRealSz( aMetric.GetFontSize() ); 2756 rFont.SetPropr( 100 ); 2757 2758 if ( aStatus.DoStretch() ) 2759 { 2760 if ( nStretchY != 100 ) 2761 { 2762 aRealSz.setHeight( aRealSz.Height() * nStretchY ); 2763 aRealSz.setHeight( aRealSz.Height() / 100 ); 2764 } 2765 if ( nStretchX != 100 ) 2766 { 2767 if ( nStretchX == nStretchY && 2768 nRelWidth == 100 ) 2769 { 2770 aRealSz.setWidth( 0 ); 2771 } 2772 else 2773 { 2774 aRealSz.setWidth( aRealSz.Width() * nStretchX ); 2775 aRealSz.setWidth( aRealSz.Width() / 100 ); 2776 2777 // Also the Kerning: (long due to handle Interim results) 2778 long nKerning = rFont.GetFixKerning(); 2779 /* 2780 The consideration was: If negative kerning, but StretchX = 200 2781 => Do not double the kerning, thus pull the letters closer together 2782 --------------------------- 2783 Kern StretchX =>Kern 2784 --------------------------- 2785 >0 <100 < (Proportional) 2786 <0 <100 < (Proportional) 2787 >0 >100 > (Proportional) 2788 <0 >100 < (The amount, thus disproportional) 2789 */ 2790 if ( ( nKerning < 0 ) && ( nStretchX > 100 ) ) 2791 { 2792 // disproportional 2793 nKerning *= 100; 2794 nKerning /= nStretchX; 2795 } 2796 else if ( nKerning ) 2797 { 2798 // Proportional 2799 nKerning *= nStretchX; 2800 nKerning /= 100; 2801 } 2802 rFont.SetFixKerning( static_cast<short>(nKerning) ); 2803 } 2804 } 2805 } 2806 if ( nRelWidth != 100 ) 2807 { 2808 aRealSz.setWidth( aRealSz.Width() * nRelWidth ); 2809 aRealSz.setWidth( aRealSz.Width() / 100 ); 2810 } 2811 rFont.SetFontSize( aRealSz ); 2812 // Font is not restored... 2813 } 2814 2815 if ( ( ( rFont.GetColor() == COL_AUTO ) || ( IsForceAutoColor() ) ) && pOut ) 2816 { 2817 // #i75566# Do not use AutoColor when printing OR Pdf export 2818 const bool bPrinting(OUTDEV_PRINTER == pOut->GetOutDevType()); 2819 const bool bPDFExporting(OUTDEV_PDF == pOut->GetOutDevType()); 2820 2821 if ( IsAutoColorEnabled() && !bPrinting && !bPDFExporting) 2822 { 2823 // Never use WindowTextColor on the printer 2824 rFont.SetColor( GetAutoColor() ); 2825 } 2826 else 2827 { 2828 if ( ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() ) 2829 rFont.SetColor( COL_WHITE ); 2830 else 2831 rFont.SetColor( COL_BLACK ); 2832 } 2833 } 2834 2835 if ( mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) && 2836 ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) ) ) 2837 { 2838 ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ]; 2839 if ( nAttr & ExtTextInputAttr::Underline ) 2840 rFont.SetUnderline( LINESTYLE_SINGLE ); 2841 else if ( nAttr & ExtTextInputAttr::BoldUnderline ) 2842 rFont.SetUnderline( LINESTYLE_BOLD ); 2843 else if ( nAttr & ExtTextInputAttr::DottedUnderline ) 2844 rFont.SetUnderline( LINESTYLE_DOTTED ); 2845 else if ( nAttr & ExtTextInputAttr::DashDotUnderline ) 2846 rFont.SetUnderline( LINESTYLE_DOTTED ); 2847 else if ( nAttr & ExtTextInputAttr::RedText ) 2848 rFont.SetColor( COL_RED ); 2849 else if ( nAttr & ExtTextInputAttr::HalfToneText ) 2850 rFont.SetColor( COL_LIGHTGRAY ); 2851 if ( nAttr & ExtTextInputAttr::Highlight ) 2852 { 2853 const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); 2854 rFont.SetColor( rStyleSettings.GetHighlightTextColor() ); 2855 rFont.SetFillColor( rStyleSettings.GetHighlightColor() ); 2856 rFont.SetTransparent( false ); 2857 } 2858 else if ( nAttr & ExtTextInputAttr::GrayWaveline ) 2859 { 2860 rFont.SetUnderline( LINESTYLE_WAVE ); 2861 if( pOut ) 2862 pOut->SetTextLineColor( COL_LIGHTGRAY ); 2863 } 2864 } 2865 } 2866 2867 void ImpEditEngine::RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics, SvxFont& rFont ) 2868 { 2869 // for line height at high / low first without Propr! 2870 sal_uInt16 nPropr = rFont.GetPropr(); 2871 DBG_ASSERT( ( nPropr == 100 ) || rFont.GetEscapement(), "Propr without Escape?!" ); 2872 if ( nPropr != 100 ) 2873 { 2874 rFont.SetPropr( 100 ); 2875 rFont.SetPhysFont( pRefDev ); 2876 } 2877 sal_uInt16 nAscent, nDescent; 2878 2879 FontMetric aMetric( pRefDev->GetFontMetric() ); 2880 nAscent = static_cast<sal_uInt16>(aMetric.GetAscent()); 2881 if ( IsAddExtLeading() ) 2882 nAscent = sal::static_int_cast< sal_uInt16 >( 2883 nAscent + aMetric.GetExternalLeading() ); 2884 nDescent = static_cast<sal_uInt16>(aMetric.GetDescent()); 2885 2886 if ( IsFixedCellHeight() ) 2887 { 2888 nAscent = sal::static_int_cast< sal_uInt16 >( rFont.GetFontHeight() ); 2889 nDescent= sal::static_int_cast< sal_uInt16 >( ImplCalculateFontIndependentLineSpacing( rFont.GetFontHeight() ) - nAscent ); 2890 } 2891 else 2892 { 2893 sal_uInt16 nIntLeading = ( aMetric.GetInternalLeading() > 0 ) ? static_cast<sal_uInt16>(aMetric.GetInternalLeading()) : 0; 2894 // Fonts without leading cause problems 2895 if ( ( nIntLeading == 0 ) && ( pRefDev->GetOutDevType() == OUTDEV_PRINTER ) ) 2896 { 2897 // Lets see what Leading one gets on the screen 2898 VclPtr<VirtualDevice> pVDev = GetVirtualDevice( pRefDev->GetMapMode(), pRefDev->GetDrawMode() ); 2899 rFont.SetPhysFont( pVDev ); 2900 aMetric = pVDev->GetFontMetric(); 2901 2902 // This is so that the Leading does not count itself out again, 2903 // if the whole line has the font, nTmpLeading. 2904 nAscent = static_cast<sal_uInt16>(aMetric.GetAscent()); 2905 nDescent = static_cast<sal_uInt16>(aMetric.GetDescent()); 2906 } 2907 } 2908 if ( nAscent > rCurMetrics.nMaxAscent ) 2909 rCurMetrics.nMaxAscent = nAscent; 2910 if ( nDescent > rCurMetrics.nMaxDescent ) 2911 rCurMetrics.nMaxDescent= nDescent; 2912 // Special treatment of high/low: 2913 if ( rFont.GetEscapement() ) 2914 { 2915 // Now in consideration of Escape/Propr 2916 // possibly enlarge Ascent or Descent 2917 short nDiff = static_cast<short>(rFont.GetFontSize().Height()*rFont.GetEscapement()/100L); 2918 if ( rFont.GetEscapement() > 0 ) 2919 { 2920 nAscent = static_cast<sal_uInt16>(static_cast<long>(nAscent)*nPropr/100 + nDiff); 2921 if ( nAscent > rCurMetrics.nMaxAscent ) 2922 rCurMetrics.nMaxAscent = nAscent; 2923 } 2924 else // has to be < 0 2925 { 2926 nDescent = static_cast<sal_uInt16>(static_cast<long>(nDescent)*nPropr/100 - nDiff); 2927 if ( nDescent > rCurMetrics.nMaxDescent ) 2928 rCurMetrics.nMaxDescent= nDescent; 2929 } 2930 } 2931 } 2932 2933 void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly, short nOrientation ) 2934 { 2935 if ( !GetUpdateMode() && !bStripOnly ) 2936 return; 2937 2938 if ( !IsFormatted() ) 2939 FormatDoc(); 2940 2941 long nFirstVisXPos = - pOutDev->GetMapMode().GetOrigin().X(); 2942 long nFirstVisYPos = - pOutDev->GetMapMode().GetOrigin().Y(); 2943 2944 const EditLine* pLine = nullptr; 2945 Point aTmpPos; 2946 Point aRedLineTmpPos; 2947 DBG_ASSERT( GetParaPortions().Count(), "No ParaPortion?!" ); 2948 SvxFont aTmpFont( GetParaPortions()[0]->GetNode()->GetCharAttribs().GetDefFont() ); 2949 vcl::Font aOldFont( pOutDev->GetFont() ); 2950 vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData* >( pOutDev->GetExtOutDevData() ); 2951 2952 // In the case of rotated text is aStartPos considered TopLeft because 2953 // other information is missing, and since the whole object is shown anyway 2954 // un-scrolled. 2955 // The rectangle is infinite. 2956 Point aOrigin( aStartPos ); 2957 double nCos = 0.0, nSin = 0.0; 2958 if ( nOrientation ) 2959 { 2960 double nRealOrientation = nOrientation*F_PI1800; 2961 nCos = cos( nRealOrientation ); 2962 nSin = sin( nRealOrientation ); 2963 } 2964 2965 // #110496# Added some more optional metafile comments. This 2966 // change: factored out some duplicated code. 2967 GDIMetaFile* pMtf = pOutDev->GetConnectMetaFile(); 2968 const bool bMetafileValid( pMtf != nullptr ); 2969 2970 long nVertLineSpacing = CalcVertLineSpacing(aStartPos); 2971 2972 2973 // Over all the paragraphs... 2974 2975 for ( sal_Int32 n = 0; n < GetParaPortions().Count(); n++ ) 2976 { 2977 const ParaPortion* pPortion = GetParaPortions()[n]; 2978 DBG_ASSERT( pPortion, "NULL-Pointer in TokenList in Paint" ); 2979 // if when typing idle formatting, asynchronous Paint. 2980 // Invisible Portions may be invalid. 2981 if ( pPortion->IsVisible() && pPortion->IsInvalid() ) 2982 return; 2983 2984 if ( pPDFExtOutDevData ) 2985 pPDFExtOutDevData->BeginStructureElement( vcl::PDFWriter::Paragraph ); 2986 2987 long nParaHeight = pPortion->GetHeight(); 2988 sal_Int32 nIndex = 0; 2989 if ( pPortion->IsVisible() && ( 2990 ( !IsVertical() && ( ( aStartPos.Y() + nParaHeight ) > aClipRect.Top() ) ) || 2991 ( IsVertical() && IsTopToBottom() && ( ( aStartPos.X() - nParaHeight ) < aClipRect.Right() ) ) || 2992 ( IsVertical() && !IsTopToBottom() && ( ( aStartPos.X() + nParaHeight ) > aClipRect.Left() ) ) ) ) 2993 2994 { 2995 2996 // Over the lines of the paragraph... 2997 2998 sal_Int32 nLines = pPortion->GetLines().Count(); 2999 sal_Int32 nLastLine = nLines-1; 3000 3001 bool bEndOfParagraphWritten(false); 3002 3003 if ( !IsVertical() ) 3004 aStartPos.AdjustY(pPortion->GetFirstLineOffset() ); 3005 else 3006 { 3007 if( IsTopToBottom() ) 3008 aStartPos.AdjustX( -(pPortion->GetFirstLineOffset()) ); 3009 else 3010 aStartPos.AdjustX(pPortion->GetFirstLineOffset() ); 3011 } 3012 3013 Point aParaStart( aStartPos ); 3014 3015 const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); 3016 sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) 3017 ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; 3018 bool bPaintBullet (false); 3019 3020 for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ ) 3021 { 3022 pLine = &pPortion->GetLines()[nLine]; 3023 nIndex = pLine->GetStart(); 3024 DBG_ASSERT( pLine, "NULL-Pointer in the line iterator in UpdateViews" ); 3025 aTmpPos = aStartPos; 3026 if ( !IsVertical() ) 3027 { 3028 aTmpPos.AdjustX(pLine->GetStartPosX() ); 3029 aTmpPos.AdjustY(pLine->GetMaxAscent() ); 3030 aStartPos.AdjustY(pLine->GetHeight() ); 3031 if (nLine != nLastLine) 3032 aStartPos.AdjustY(nVertLineSpacing ); 3033 } 3034 else 3035 { 3036 if ( IsTopToBottom() ) 3037 { 3038 aTmpPos.AdjustY(pLine->GetStartPosX() ); 3039 aTmpPos.AdjustX( -(pLine->GetMaxAscent()) ); 3040 aStartPos.AdjustX( -(pLine->GetHeight()) ); 3041 if (nLine != nLastLine) 3042 aStartPos.AdjustX( -nVertLineSpacing ); 3043 } 3044 else 3045 { 3046 aTmpPos.AdjustY( -(pLine->GetStartPosX()) ); 3047 aTmpPos.AdjustX(pLine->GetMaxAscent() ); 3048 aStartPos.AdjustX(pLine->GetHeight() ); 3049 if (nLine != nLastLine) 3050 aStartPos.AdjustX(nVertLineSpacing ); 3051 } 3052 } 3053 3054 if ( ( !IsVertical() && ( aStartPos.Y() > aClipRect.Top() ) ) 3055 || ( IsVertical() && IsTopToBottom() && aStartPos.X() < aClipRect.Right() ) 3056 || ( IsVertical() && !IsTopToBottom() && aStartPos.X() > aClipRect.Left() ) ) 3057 { 3058 bPaintBullet = false; 3059 3060 // Why not just also call when stripping portions? This will give the correct values 3061 // and needs no position corrections in OutlinerEditEng::DrawingText which tries to call 3062 // PaintBullet correctly; exactly what GetEditEnginePtr()->PaintingFirstLine 3063 // does, too. No change for not-layouting (painting). 3064 if(0 == nLine) // && !bStripOnly) 3065 { 3066 GetEditEnginePtr()->PaintingFirstLine( n, aParaStart, aTmpPos.Y(), aOrigin, nOrientation, pOutDev ); 3067 3068 // Remember whether a bullet was painted. 3069 const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib(n, EE_PARA_BULLETSTATE); 3070 bPaintBullet = rBulletState.GetValue(); 3071 } 3072 3073 3074 // Over the Portions of the line... 3075 3076 bool bParsingFields = false; 3077 std::vector< sal_Int32 >::iterator itSubLines; 3078 3079 for ( sal_Int32 nPortion = pLine->GetStartPortion(); nPortion <= pLine->GetEndPortion(); nPortion++ ) 3080 { 3081 DBG_ASSERT( pPortion->GetTextPortions().Count(), "Line without Textportion in Paint!" ); 3082 const TextPortion& rTextPortion = pPortion->GetTextPortions()[nPortion]; 3083 3084 long nPortionXOffset = GetPortionXOffset( pPortion, pLine, nPortion ); 3085 if ( !IsVertical() ) 3086 { 3087 aTmpPos.setX( aStartPos.X() + nPortionXOffset ); 3088 if ( aTmpPos.X() > aClipRect.Right() ) 3089 break; // No further output in line necessary 3090 } 3091 else 3092 { 3093 if( IsTopToBottom() ) 3094 { 3095 aTmpPos.setY( aStartPos.Y() + nPortionXOffset ); 3096 if ( aTmpPos.Y() > aClipRect.Bottom() ) 3097 break; // No further output in line necessary 3098 } 3099 else 3100 { 3101 aTmpPos.setY( aStartPos.Y() - nPortionXOffset ); 3102 if (aTmpPos.Y() < aClipRect.Top()) 3103 break; // No further output in line necessary 3104 } 3105 } 3106 3107 switch ( rTextPortion.GetKind() ) 3108 { 3109 case PortionKind::TEXT: 3110 case PortionKind::FIELD: 3111 case PortionKind::HYPHENATOR: 3112 { 3113 SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, pOutDev ); 3114 3115 bool bDrawFrame = false; 3116 3117 if ( ( rTextPortion.GetKind() == PortionKind::FIELD ) && !aTmpFont.IsTransparent() && 3118 ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() && 3119 ( IsAutoColorEnabled() && ( pOutDev->GetOutDevType() != OUTDEV_PRINTER ) ) ) 3120 { 3121 aTmpFont.SetTransparent( true ); 3122 pOutDev->SetFillColor(); 3123 pOutDev->SetLineColor( GetAutoColor() ); 3124 bDrawFrame = true; 3125 } 3126 3127 #if OSL_DEBUG_LEVEL > 2 3128 // Do we really need this if statement? 3129 if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR ) 3130 { 3131 aTmpFont.SetFillColor( COL_LIGHTGRAY ); 3132 aTmpFont.SetTransparent( sal_False ); 3133 } 3134 if ( rTextPortion.IsRightToLeft() ) 3135 { 3136 aTmpFont.SetFillColor( COL_LIGHTGRAY ); 3137 aTmpFont.SetTransparent( sal_False ); 3138 } 3139 else if ( GetI18NScriptType( EditPaM( pPortion->GetNode(), nIndex+1 ) ) == i18n::ScriptType::COMPLEX ) 3140 { 3141 aTmpFont.SetFillColor( COL_LIGHTCYAN ); 3142 aTmpFont.SetTransparent( sal_False ); 3143 } 3144 #endif 3145 aTmpFont.SetPhysFont( pOutDev ); 3146 3147 // #114278# Saving both layout mode and language (since I'm 3148 // potentially changing both) 3149 pOutDev->Push( PushFlags::TEXTLAYOUTMODE|PushFlags::TEXTLANGUAGE ); 3150 ImplInitLayoutMode( pOutDev, n, nIndex ); 3151 ImplInitDigitMode(pOutDev, aTmpFont.GetLanguage()); 3152 3153 OUString aText; 3154 sal_Int32 nTextStart = 0; 3155 sal_Int32 nTextLen = 0; 3156 const long* pDXArray = nullptr; 3157 std::unique_ptr<long[]> pTmpDXArray; 3158 3159 if ( rTextPortion.GetKind() == PortionKind::TEXT ) 3160 { 3161 aText = pPortion->GetNode()->GetString(); 3162 nTextStart = nIndex; 3163 nTextLen = rTextPortion.GetLen(); 3164 pDXArray = pLine->GetCharPosArray().data() + (nIndex - pLine->GetStart()); 3165 3166 // Paint control characters (#i55716#) 3167 /* XXX: Given that there's special handling 3168 * only for some specific characters 3169 * (U+200B ZERO WIDTH SPACE and U+2060 WORD 3170 * JOINER) it is assumed to be not relevant 3171 * for MarkUrlFields(). */ 3172 if ( aStatus.MarkNonUrlFields() ) 3173 { 3174 sal_Int32 nTmpIdx; 3175 const sal_Int32 nTmpEnd = nTextStart + rTextPortion.GetLen(); 3176 3177 for ( nTmpIdx = nTextStart; nTmpIdx <= nTmpEnd ; ++nTmpIdx ) 3178 { 3179 const sal_Unicode cChar = ( nTmpIdx != aText.getLength() && ( nTmpIdx != nTextStart || 0 == nTextStart ) ) ? 3180 aText[nTmpIdx] : 3181 0; 3182 3183 if ( 0x200B == cChar || 0x2060 == cChar ) 3184 { 3185 const OUString aBlank( ' ' ); 3186 long nHalfBlankWidth = aTmpFont.QuickGetTextSize( pOutDev, aBlank, 0, 1 ).Width() / 2; 3187 3188 const long nAdvanceX = ( nTmpIdx == nTmpEnd ? 3189 rTextPortion.GetSize().Width() : 3190 pDXArray[ nTmpIdx - nTextStart ] ) - nHalfBlankWidth; 3191 const long nAdvanceY = -pLine->GetMaxAscent(); 3192 3193 Point aTopLeftRectPos( aTmpPos ); 3194 if ( !IsVertical() ) 3195 { 3196 aTopLeftRectPos.AdjustX(nAdvanceX ); 3197 aTopLeftRectPos.AdjustY(nAdvanceY ); 3198 } 3199 else 3200 { 3201 if( IsTopToBottom() ) 3202 { 3203 aTopLeftRectPos.AdjustY( -nAdvanceX ); 3204 aTopLeftRectPos.AdjustX(nAdvanceY ); 3205 } 3206 else 3207 { 3208 aTopLeftRectPos.AdjustY(nAdvanceX ); 3209 aTopLeftRectPos.AdjustX( -nAdvanceY ); 3210 } 3211 } 3212 3213 Point aBottomRightRectPos( aTopLeftRectPos ); 3214 if ( !IsVertical() ) 3215 { 3216 aBottomRightRectPos.AdjustX(2 * nHalfBlankWidth ); 3217 aBottomRightRectPos.AdjustY(pLine->GetHeight() ); 3218 } 3219 else 3220 { 3221 if (IsTopToBottom()) 3222 { 3223 aBottomRightRectPos.AdjustX(pLine->GetHeight() ); 3224 aBottomRightRectPos.AdjustY( -(2 * nHalfBlankWidth) ); 3225 } 3226 else 3227 { 3228 aBottomRightRectPos.AdjustX( -(pLine->GetHeight()) ); 3229 aBottomRightRectPos.AdjustY(2 * nHalfBlankWidth ); 3230 } 3231 } 3232 3233 pOutDev->Push( PushFlags::FILLCOLOR ); 3234 pOutDev->Push( PushFlags::LINECOLOR ); 3235 pOutDev->SetFillColor( COL_LIGHTGRAY ); 3236 pOutDev->SetLineColor( COL_LIGHTGRAY ); 3237 3238 const tools::Rectangle aBackRect( aTopLeftRectPos, aBottomRightRectPos ); 3239 pOutDev->DrawRect( aBackRect ); 3240 3241 pOutDev->Pop(); 3242 pOutDev->Pop(); 3243 3244 if ( 0x200B == cChar ) 3245 { 3246 const OUString aSlash( '/' ); 3247 const short nOldEscapement = aTmpFont.GetEscapement(); 3248 const sal_uInt8 nOldPropr = aTmpFont.GetPropr(); 3249 3250 aTmpFont.SetEscapement( -20 ); 3251 aTmpFont.SetPropr( 25 ); 3252 aTmpFont.SetPhysFont( pOutDev ); 3253 3254 const Size aSlashSize = aTmpFont.QuickGetTextSize( pOutDev, aSlash, 0, 1 ); 3255 Point aSlashPos( aTmpPos ); 3256 const long nAddX = nHalfBlankWidth - aSlashSize.Width() / 2; 3257 if ( !IsVertical() ) 3258 { 3259 aSlashPos.setX( aTopLeftRectPos.X() + nAddX ); 3260 } 3261 else 3262 { 3263 if (IsTopToBottom()) 3264 aSlashPos.setY( aTopLeftRectPos.Y() + nAddX ); 3265 else 3266 aSlashPos.setY( aTopLeftRectPos.Y() - nAddX ); 3267 } 3268 3269 aTmpFont.QuickDrawText( pOutDev, aSlashPos, aSlash, 0, 1 ); 3270 3271 aTmpFont.SetEscapement( nOldEscapement ); 3272 aTmpFont.SetPropr( nOldPropr ); 3273 aTmpFont.SetPhysFont( pOutDev ); 3274 } 3275 } 3276 } 3277 } 3278 } 3279 else if ( rTextPortion.GetKind() == PortionKind::FIELD ) 3280 { 3281 const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); 3282 DBG_ASSERT( pAttr, "Field not found"); 3283 DBG_ASSERT( pAttr && dynamic_cast< const SvxFieldItem* >( pAttr->GetItem() ) != nullptr, "Field of the wrong type! "); 3284 aText = static_cast<const EditCharAttribField*>(pAttr)->GetFieldValue(); 3285 nTextStart = 0; 3286 nTextLen = aText.getLength(); 3287 ExtraPortionInfo *pExtraInfo = rTextPortion.GetExtraInfos(); 3288 // Do not split the Fields into different lines while editing 3289 // With EditView on Overlay bStripOnly is now set for stripping to 3290 // primitives. To stay compatible in EditMode use pActiveView to detect 3291 // when we are in EditMode. For whatever reason URLs are drawn as single 3292 // line in edit mode, originally clipped against edit area (which is no 3293 // longer done in Overlay mode and allows to *read* the URL). 3294 // It would be difficult to change this due to needed adaptations in 3295 // EditEngine (look for lineBreaksList creation) 3296 if( nullptr == pActiveView && bStripOnly && !bParsingFields && pExtraInfo && !pExtraInfo->lineBreaksList.empty() ) 3297 { 3298 bParsingFields = true; 3299 itSubLines = pExtraInfo->lineBreaksList.begin(); 3300 } 3301 3302 if( bParsingFields ) 3303 { 3304 if( itSubLines != pExtraInfo->lineBreaksList.begin() ) 3305 { 3306 // only use GetMaxAscent(), pLine->GetHeight() will not 3307 // proceed as needed (see PortionKind::TEXT above and nAdvanceY) 3308 // what will lead to a compressed look with multiple lines 3309 const sal_uInt16 nMaxAscent(pLine->GetMaxAscent()); 3310 3311 if ( !IsVertical() ) 3312 { 3313 aStartPos.AdjustY(nMaxAscent ); 3314 aTmpPos.AdjustY(nMaxAscent ); 3315 } 3316 else 3317 { 3318 if (IsTopToBottom()) 3319 { 3320 aTmpPos.AdjustX( -nMaxAscent ); 3321 aStartPos.AdjustX( -nMaxAscent ); 3322 } 3323 else 3324 { 3325 aTmpPos.AdjustX(nMaxAscent ); 3326 aStartPos.AdjustX(nMaxAscent ); 3327 } 3328 } 3329 } 3330 std::vector< sal_Int32 >::iterator curIt = itSubLines; 3331 ++itSubLines; 3332 if( itSubLines != pExtraInfo->lineBreaksList.end() ) 3333 { 3334 nTextStart = *curIt; 3335 nTextLen = *itSubLines - nTextStart; 3336 } 3337 else 3338 { 3339 nTextStart = *curIt; 3340 nTextLen = nTextLen - nTextStart; 3341 bParsingFields = false; 3342 } 3343 } 3344 3345 pTmpDXArray.reset(new long[ aText.getLength() ]); 3346 pDXArray = pTmpDXArray.get(); 3347 vcl::Font _aOldFont( GetRefDevice()->GetFont() ); 3348 aTmpFont.SetPhysFont( GetRefDevice() ); 3349 aTmpFont.QuickGetTextSize( GetRefDevice(), aText, nTextStart, nTextLen, pTmpDXArray.get() ); 3350 3351 // add a meta file comment if we record to a metafile 3352 if( bMetafileValid ) 3353 { 3354 const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); 3355 if( pFieldItem ) 3356 { 3357 const SvxFieldData* pFieldData = pFieldItem->GetField(); 3358 if( pFieldData ) 3359 pMtf->AddAction( pFieldData->createBeginComment() ); 3360 } 3361 } 3362 3363 } 3364 else if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR ) 3365 { 3366 if ( rTextPortion.GetExtraValue() ) 3367 aText = OUString(rTextPortion.GetExtraValue()); 3368 aText += OUStringLiteral1(CH_HYPH); 3369 nTextStart = 0; 3370 nTextLen = aText.getLength(); 3371 3372 // crash when accessing 0 pointer in pDXArray 3373 pTmpDXArray.reset(new long[ aText.getLength() ]); 3374 pDXArray = pTmpDXArray.get(); 3375 vcl::Font _aOldFont( GetRefDevice()->GetFont() ); 3376 aTmpFont.SetPhysFont( GetRefDevice() ); 3377 aTmpFont.QuickGetTextSize( GetRefDevice(), aText, 0, aText.getLength(), pTmpDXArray.get() ); 3378 } 3379 3380 long nTxtWidth = rTextPortion.GetSize().Width(); 3381 3382 Point aOutPos( aTmpPos ); 3383 aRedLineTmpPos = aTmpPos; 3384 // In RTL portions spell markup pos should be at the start of the 3385 // first chara as well. That is on the right end of the portion 3386 if (rTextPortion.IsRightToLeft()) 3387 aRedLineTmpPos.AdjustX(rTextPortion.GetSize().Width() ); 3388 3389 if ( bStripOnly ) 3390 { 3391 EEngineData::WrongSpellVector aWrongSpellVector; 3392 3393 if(GetStatus().DoOnlineSpelling() && rTextPortion.GetLen()) 3394 { 3395 WrongList* pWrongs = pPortion->GetNode()->GetWrongList(); 3396 3397 if(pWrongs && !pWrongs->empty()) 3398 { 3399 size_t nStart = nIndex, nEnd = 0; 3400 bool bWrong = pWrongs->NextWrong(nStart, nEnd); 3401 const size_t nMaxEnd(nIndex + rTextPortion.GetLen()); 3402 3403 while(bWrong) 3404 { 3405 if(nStart >= nMaxEnd) 3406 { 3407 break; 3408 } 3409 3410 if(nStart < static_cast<size_t>(nIndex)) 3411 { 3412 nStart = nIndex; 3413 } 3414 3415 if(nEnd > nMaxEnd) 3416 { 3417 nEnd = nMaxEnd; 3418 } 3419 3420 // add to vector 3421 aWrongSpellVector.emplace_back(nStart, nEnd); 3422 3423 // goto next index 3424 nStart = nEnd + 1; 3425 3426 if(nEnd < nMaxEnd) 3427 { 3428 bWrong = pWrongs->NextWrong(nStart, nEnd); 3429 } 3430 else 3431 { 3432 bWrong = false; 3433 } 3434 } 3435 } 3436 } 3437 3438 const SvxFieldData* pFieldData = nullptr; 3439 3440 if(PortionKind::FIELD == rTextPortion.GetKind()) 3441 { 3442 const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); 3443 const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); 3444 3445 if(pFieldItem) 3446 { 3447 pFieldData = pFieldItem->GetField(); 3448 } 3449 } 3450 3451 // support for EOC, EOW, EOS TEXT comments. To support that, 3452 // the locale is needed. With the locale and a XBreakIterator it is 3453 // possible to re-create the text marking info on primitive level 3454 const lang::Locale aLocale(GetLocale(EditPaM(pPortion->GetNode(), nIndex + 1))); 3455 3456 // create EOL and EOP bools 3457 const bool bEndOfLine(nPortion == pLine->GetEndPortion()); 3458 const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines); 3459 3460 // get Overline color (from ((const SvxOverlineItem*)GetItem())->GetColor() in 3461 // consequence, but also already set at pOutDev) 3462 const Color aOverlineColor(pOutDev->GetOverlineColor()); 3463 3464 // get TextLine color (from ((const SvxUnderlineItem*)GetItem())->GetColor() in 3465 // consequence, but also already set at pOutDev) 3466 const Color aTextLineColor(pOutDev->GetTextLineColor()); 3467 3468 // Unicode code points conversion according to ctl text numeral setting 3469 aText = convertDigits(aText, nTextStart, nTextLen, 3470 ImplCalcDigitLang(aTmpFont.GetLanguage())); 3471 3472 // StripPortions() data callback 3473 GetEditEnginePtr()->DrawingText( aOutPos, aText, nTextStart, nTextLen, pDXArray, 3474 aTmpFont, n, rTextPortion.GetRightToLeftLevel(), 3475 !aWrongSpellVector.empty() ? &aWrongSpellVector : nullptr, 3476 pFieldData, 3477 bEndOfLine, bEndOfParagraph, // support for EOL/EOP TEXT comments 3478 &aLocale, 3479 aOverlineColor, 3480 aTextLineColor); 3481 3482 // #108052# remember that EOP is written already for this ParaPortion 3483 if(bEndOfParagraph) 3484 { 3485 bEndOfParagraphWritten = true; 3486 } 3487 } 3488 else 3489 { 3490 short nEsc = aTmpFont.GetEscapement(); 3491 if ( nOrientation ) 3492 { 3493 // In case of high/low do it yourself: 3494 if ( aTmpFont.GetEscapement() ) 3495 { 3496 long nDiff = aTmpFont.GetFontSize().Height() * aTmpFont.GetEscapement() / 100L; 3497 if ( !IsVertical() ) 3498 aOutPos.AdjustY( -nDiff ); 3499 else 3500 { 3501 if (IsTopToBottom()) 3502 aOutPos.AdjustX(nDiff ); 3503 else 3504 aOutPos.AdjustX( -nDiff ); 3505 } 3506 aRedLineTmpPos = aOutPos; 3507 aTmpFont.SetEscapement( 0 ); 3508 } 3509 3510 aOutPos = lcl_ImplCalcRotatedPos( aOutPos, aOrigin, nSin, nCos ); 3511 aTmpFont.SetOrientation( aTmpFont.GetOrientation()+nOrientation ); 3512 aTmpFont.SetPhysFont( pOutDev ); 3513 3514 } 3515 3516 // Take only what begins in the visible range: 3517 // Important, because of a bug in some graphic cards 3518 // when transparent font, output when negative 3519 if ( nOrientation || ( !IsVertical() && ( ( aTmpPos.X() + nTxtWidth ) >= nFirstVisXPos ) ) 3520 || ( IsVertical() && ( ( aTmpPos.Y() + nTxtWidth ) >= nFirstVisYPos ) ) ) 3521 { 3522 if ( nEsc && ( aTmpFont.GetUnderline() != LINESTYLE_NONE ) ) 3523 { 3524 // Paint the high/low without underline, 3525 // Display the Underline on the 3526 // base line of the original font height... 3527 // But only if there was something underlined before! 3528 bool bSpecialUnderline = false; 3529 EditCharAttrib* pPrev = pPortion->GetNode()->GetCharAttribs().FindAttrib( EE_CHAR_ESCAPEMENT, nIndex ); 3530 if ( pPrev ) 3531 { 3532 SvxFont aDummy; 3533 // Underscore in front? 3534 if ( pPrev->GetStart() ) 3535 { 3536 SeekCursor( pPortion->GetNode(), pPrev->GetStart(), aDummy ); 3537 if ( aDummy.GetUnderline() != LINESTYLE_NONE ) 3538 bSpecialUnderline = true; 3539 } 3540 if ( !bSpecialUnderline && ( pPrev->GetEnd() < pPortion->GetNode()->Len() ) ) 3541 { 3542 SeekCursor( pPortion->GetNode(), pPrev->GetEnd()+1, aDummy ); 3543 if ( aDummy.GetUnderline() != LINESTYLE_NONE ) 3544 bSpecialUnderline = true; 3545 } 3546 } 3547 if ( bSpecialUnderline ) 3548 { 3549 Size aSz = aTmpFont.GetPhysTxtSize( pOutDev, aText, nTextStart, nTextLen ); 3550 sal_uInt8 nProp = aTmpFont.GetPropr(); 3551 aTmpFont.SetEscapement( 0 ); 3552 aTmpFont.SetPropr( 100 ); 3553 aTmpFont.SetPhysFont( pOutDev ); 3554 OUStringBuffer aBlanks; 3555 comphelper::string::padToLength( aBlanks, nTextLen, ' ' ); 3556 Point aUnderlinePos( aOutPos ); 3557 if ( nOrientation ) 3558 aUnderlinePos = lcl_ImplCalcRotatedPos( aTmpPos, aOrigin, nSin, nCos ); 3559 pOutDev->DrawStretchText( aUnderlinePos, aSz.Width(), aBlanks.makeStringAndClear(), 0, nTextLen ); 3560 3561 aTmpFont.SetUnderline( LINESTYLE_NONE ); 3562 if ( !nOrientation ) 3563 aTmpFont.SetEscapement( nEsc ); 3564 aTmpFont.SetPropr( nProp ); 3565 aTmpFont.SetPhysFont( pOutDev ); 3566 } 3567 } 3568 Point aRealOutPos( aOutPos ); 3569 if ( ( rTextPortion.GetKind() == PortionKind::TEXT ) 3570 && rTextPortion.GetExtraInfos() && rTextPortion.GetExtraInfos()->bCompressed 3571 && rTextPortion.GetExtraInfos()->bFirstCharIsRightPunktuation ) 3572 { 3573 aRealOutPos.AdjustX(rTextPortion.GetExtraInfos()->nPortionOffsetX ); 3574 } 3575 3576 // RTL portions with (#i37132#) 3577 // compressed blank should not paint this blank: 3578 if ( rTextPortion.IsRightToLeft() && nTextLen >= 2 && 3579 pDXArray[ nTextLen - 1 ] == 3580 pDXArray[ nTextLen - 2 ] && 3581 ' ' == aText[nTextStart + nTextLen - 1] ) 3582 --nTextLen; 3583 3584 // output directly 3585 aTmpFont.QuickDrawText( pOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray ); 3586 3587 if ( bDrawFrame ) 3588 { 3589 Point aTopLeft( aTmpPos ); 3590 aTopLeft.AdjustY( -(pLine->GetMaxAscent()) ); 3591 if ( nOrientation ) 3592 aTopLeft = lcl_ImplCalcRotatedPos( aTopLeft, aOrigin, nSin, nCos ); 3593 tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() ); 3594 pOutDev->DrawRect( aRect ); 3595 } 3596 3597 // PDF export: 3598 if ( pPDFExtOutDevData ) 3599 { 3600 if ( rTextPortion.GetKind() == PortionKind::FIELD ) 3601 { 3602 const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); 3603 const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); 3604 if( pFieldItem ) 3605 { 3606 const SvxFieldData* pFieldData = pFieldItem->GetField(); 3607 if ( auto pUrlField = dynamic_cast< const SvxURLField* >( pFieldData ) ) 3608 { 3609 Point aTopLeft( aTmpPos ); 3610 aTopLeft.AdjustY( -(pLine->GetMaxAscent()) ); 3611 3612 tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() ); 3613 vcl::PDFExtOutDevBookmarkEntry aBookmark; 3614 aBookmark.nLinkId = pPDFExtOutDevData->CreateLink( aRect ); 3615 aBookmark.aBookmark = pUrlField->GetURL(); 3616 std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks(); 3617 rBookmarks.push_back( aBookmark ); 3618 } 3619 } 3620 } 3621 } 3622 } 3623 3624 const WrongList* const pWrongList = pPortion->GetNode()->GetWrongList(); 3625 if ( GetStatus().DoOnlineSpelling() && pWrongList && !pWrongList->empty() && rTextPortion.GetLen() ) 3626 { 3627 {//#105750# adjust LinePos for superscript or subscript text 3628 short _nEsc = aTmpFont.GetEscapement(); 3629 if( _nEsc ) 3630 { 3631 long nShift = (_nEsc * aTmpFont.GetFontSize().Height()) / 100L; 3632 if( !IsVertical() ) 3633 aRedLineTmpPos.AdjustY( -nShift ); 3634 else 3635 if (IsTopToBottom()) 3636 aRedLineTmpPos.AdjustX(nShift ); 3637 else 3638 aRedLineTmpPos.AdjustX( -nShift ); 3639 } 3640 } 3641 Color aOldColor( pOutDev->GetLineColor() ); 3642 pOutDev->SetLineColor( GetColorConfig().GetColorValue( svtools::SPELL ).nColor ); 3643 lcl_DrawRedLines( pOutDev, aTmpFont.GetFontSize().Height(), aRedLineTmpPos, static_cast<size_t>(nIndex), static_cast<size_t>(nIndex) + rTextPortion.GetLen(), pDXArray, pPortion->GetNode()->GetWrongList(), nOrientation, aOrigin, IsVertical(), rTextPortion.IsRightToLeft() ); 3644 pOutDev->SetLineColor( aOldColor ); 3645 } 3646 } 3647 3648 pOutDev->Pop(); 3649 3650 pTmpDXArray.reset(); 3651 3652 if ( rTextPortion.GetKind() == PortionKind::FIELD ) 3653 { 3654 const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); 3655 DBG_ASSERT( pAttr, "Field not found" ); 3656 DBG_ASSERT( pAttr && dynamic_cast< const SvxFieldItem* >( pAttr->GetItem() ) != nullptr, "Wrong type of field!" ); 3657 3658 // add a meta file comment if we record to a metafile 3659 if( bMetafileValid ) 3660 { 3661 const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); 3662 3663 if( pFieldItem ) 3664 { 3665 const SvxFieldData* pFieldData = pFieldItem->GetField(); 3666 if( pFieldData ) 3667 pMtf->AddAction( SvxFieldData::createEndComment() ); 3668 } 3669 } 3670 3671 } 3672 3673 } 3674 break; 3675 case PortionKind::TAB: 3676 { 3677 if ( rTextPortion.GetExtraValue() && ( rTextPortion.GetExtraValue() != ' ' ) ) 3678 { 3679 SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, pOutDev ); 3680 aTmpFont.SetTransparent( false ); 3681 aTmpFont.SetEscapement( 0 ); 3682 aTmpFont.SetPhysFont( pOutDev ); 3683 long nCharWidth = aTmpFont.QuickGetTextSize( pOutDev, 3684 OUString(rTextPortion.GetExtraValue()), 0, 1 ).Width(); 3685 sal_Int32 nChars = 2; 3686 if( nCharWidth ) 3687 nChars = rTextPortion.GetSize().Width() / nCharWidth; 3688 if ( nChars < 2 ) 3689 nChars = 2; // is compressed by DrawStretchText. 3690 else if ( nChars == 2 ) 3691 nChars = 3; // looks better 3692 3693 OUStringBuffer aBuf; 3694 comphelper::string::padToLength(aBuf, nChars, rTextPortion.GetExtraValue()); 3695 OUString aText(aBuf.makeStringAndClear()); 3696 aTmpFont.QuickDrawText( pOutDev, aTmpPos, aText, 0, aText.getLength() ); 3697 pOutDev->DrawStretchText( aTmpPos, rTextPortion.GetSize().Width(), aText ); 3698 3699 if ( bStripOnly ) 3700 { 3701 // create EOL and EOP bools 3702 const bool bEndOfLine(nPortion == pLine->GetEndPortion()); 3703 const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines); 3704 3705 const Color aOverlineColor(pOutDev->GetOverlineColor()); 3706 const Color aTextLineColor(pOutDev->GetTextLineColor()); 3707 3708 // StripPortions() data callback 3709 GetEditEnginePtr()->DrawingTab( aTmpPos, 3710 rTextPortion.GetSize().Width(), 3711 OUString(rTextPortion.GetExtraValue()), 3712 aTmpFont, n, rTextPortion.GetRightToLeftLevel(), 3713 bEndOfLine, bEndOfParagraph, 3714 aOverlineColor, aTextLineColor); 3715 } 3716 } 3717 else if ( bStripOnly ) 3718 { 3719 // #i108052# When stripping, a callback for _empty_ paragraphs is also needed. 3720 // This was optimized away (by not rendering the space-only tab portion), so do 3721 // it manually here. 3722 const bool bEndOfLine(nPortion == pLine->GetEndPortion()); 3723 const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines); 3724 3725 const Color aOverlineColor(pOutDev->GetOverlineColor()); 3726 const Color aTextLineColor(pOutDev->GetTextLineColor()); 3727 3728 GetEditEnginePtr()->DrawingText( 3729 aTmpPos, OUString(), 0, 0, nullptr, 3730 aTmpFont, n, 0, 3731 nullptr, 3732 nullptr, 3733 bEndOfLine, bEndOfParagraph, 3734 nullptr, 3735 aOverlineColor, 3736 aTextLineColor); 3737 } 3738 } 3739 break; 3740 case PortionKind::LINEBREAK: break; 3741 } 3742 if( bParsingFields ) 3743 nPortion--; 3744 else 3745 nIndex = nIndex + rTextPortion.GetLen(); 3746 3747 } 3748 } 3749 3750 if ( ( nLine != nLastLine ) && !aStatus.IsOutliner() ) 3751 { 3752 if ( !IsVertical() ) 3753 aStartPos.AdjustY(nSBL ); 3754 else 3755 { 3756 if( IsTopToBottom() ) 3757 aStartPos.AdjustX( -nSBL ); 3758 else 3759 aStartPos.AdjustX(nSBL ); 3760 } 3761 } 3762 3763 // no more visible actions? 3764 if ( !IsVertical() && ( aStartPos.Y() >= aClipRect.Bottom() ) ) 3765 break; 3766 else if ( IsVertical() && IsTopToBottom() && ( aStartPos.X() <= aClipRect.Left() ) ) 3767 break; 3768 else if (IsVertical() && !IsTopToBottom() && (aStartPos.X() >= aClipRect.Right())) 3769 break; 3770 } 3771 3772 if ( !aStatus.IsOutliner() ) 3773 { 3774 const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); 3775 long nUL = GetYValue( rULItem.GetLower() ); 3776 if ( !IsVertical() ) 3777 aStartPos.AdjustY(nUL ); 3778 else 3779 { 3780 if (IsTopToBottom()) 3781 aStartPos.AdjustX( -nUL ); 3782 else 3783 aStartPos.AdjustX(nUL ); 3784 } 3785 } 3786 3787 // #108052# Safer way for #i108052# and #i118881#: If for the current ParaPortion 3788 // EOP is not written, do it now. This will be safer than before. It has shown 3789 // that the reason for #i108052# was fixed/removed again, so this is a try to fix 3790 // the number of paragraphs (and counting empty ones) now independent from the 3791 // changes in EditEngine behaviour. 3792 if(!bEndOfParagraphWritten && !bPaintBullet && bStripOnly) 3793 { 3794 const Color aOverlineColor(pOutDev->GetOverlineColor()); 3795 const Color aTextLineColor(pOutDev->GetTextLineColor()); 3796 3797 GetEditEnginePtr()->DrawingText( 3798 aTmpPos, OUString(), 0, 0, nullptr, 3799 aTmpFont, n, 0, 3800 nullptr, 3801 nullptr, 3802 false, true, // support for EOL/EOP TEXT comments 3803 nullptr, 3804 aOverlineColor, 3805 aTextLineColor); 3806 } 3807 } 3808 else 3809 { 3810 if ( !IsVertical() ) 3811 aStartPos.AdjustY(nParaHeight ); 3812 else 3813 { 3814 if (IsTopToBottom()) 3815 aStartPos.AdjustX( -nParaHeight ); 3816 else 3817 aStartPos.AdjustX(nParaHeight ); 3818 } 3819 } 3820 3821 if ( pPDFExtOutDevData ) 3822 pPDFExtOutDevData->EndStructureElement(); 3823 3824 // no more visible actions? 3825 if ( !IsVertical() && ( aStartPos.Y() > aClipRect.Bottom() ) ) 3826 break; 3827 if ( IsVertical() && IsTopToBottom() && ( aStartPos.X() < aClipRect.Left() ) ) 3828 break; 3829 if (IsVertical() && !IsTopToBottom() && ( aStartPos.X() > aClipRect.Right() ) ) 3830 break; 3831 } 3832 } 3833 3834 void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice ) 3835 { 3836 DBG_ASSERT( pView, "No View - No Paint!" ); 3837 3838 if ( !GetUpdateMode() || IsInUndo() ) 3839 return; 3840 3841 // Intersection of paint area and output area. 3842 tools::Rectangle aClipRect( pView->GetOutputArea() ); 3843 aClipRect.Intersection( rRect ); 3844 3845 OutputDevice* pTarget = pTargetDevice ? pTargetDevice : pView->GetWindow(); 3846 3847 Point aStartPos; 3848 if ( !IsVertical() ) 3849 { 3850 aStartPos = pView->GetOutputArea().TopLeft(); 3851 aStartPos.AdjustX( -(pView->GetVisDocLeft()) ); 3852 aStartPos.AdjustY( -(pView->GetVisDocTop()) ); 3853 } 3854 else 3855 { 3856 if( IsTopToBottom() ) 3857 { 3858 aStartPos = pView->GetOutputArea().TopRight(); 3859 aStartPos.AdjustX(pView->GetVisDocTop() ); 3860 aStartPos.AdjustY( -(pView->GetVisDocLeft()) ); 3861 } 3862 else 3863 { 3864 aStartPos = pView->GetOutputArea().BottomLeft(); 3865 aStartPos.AdjustX( -(pView->GetVisDocTop()) ); 3866 aStartPos.AdjustY(pView->GetVisDocLeft() ); 3867 } 3868 } 3869 3870 // If Doc-width < Output Area,Width and not wrapped fields, 3871 // the fields usually protrude if > line. 3872 // (Not at the top, since there the Doc-width from formatting is already 3873 // there) 3874 if ( !IsVertical() && ( pView->GetOutputArea().GetWidth() > GetPaperSize().Width() ) ) 3875 { 3876 long nMaxX = pView->GetOutputArea().Left() + GetPaperSize().Width(); 3877 if ( aClipRect.Left() > nMaxX ) 3878 return; 3879 if ( aClipRect.Right() > nMaxX ) 3880 aClipRect.SetRight( nMaxX ); 3881 } 3882 3883 bool bClipRegion = pTarget->IsClipRegion(); 3884 vcl::Region aOldRegion = pTarget->GetClipRegion(); 3885 pTarget->IntersectClipRegion( aClipRect ); 3886 3887 Paint( pTarget, aClipRect, aStartPos ); 3888 3889 if ( bClipRegion ) 3890 pTarget->SetClipRegion( aOldRegion ); 3891 else 3892 pTarget->SetClipRegion(); 3893 3894 // In case of tiled rendering pass a region to DrawSelectionXOR(), so that 3895 // selection callbacks are not emitted during every repaint. 3896 vcl::Region aRegion; 3897 pView->DrawSelectionXOR(pView->GetEditSelection(), comphelper::LibreOfficeKit::isActive() ? &aRegion : nullptr, pTarget); 3898 } 3899 3900 void ImpEditEngine::InsertContent( ContentNode* pNode, sal_Int32 nPos ) 3901 { 3902 DBG_ASSERT( pNode, "NULL-Pointer in InsertContent! " ); 3903 DBG_ASSERT( IsInUndo(), "InsertContent only for Undo()!" ); 3904 GetParaPortions().Insert(nPos, std::make_unique<ParaPortion>( pNode )); 3905 aEditDoc.Insert(nPos, pNode); 3906 if ( IsCallParaInsertedOrDeleted() ) 3907 GetEditEnginePtr()->ParagraphInserted( nPos ); 3908 } 3909 3910 EditPaM ImpEditEngine::SplitContent( sal_Int32 nNode, sal_Int32 nSepPos ) 3911 { 3912 ContentNode* pNode = aEditDoc.GetObject( nNode ); 3913 DBG_ASSERT( pNode, "Invalid Node in SplitContent" ); 3914 DBG_ASSERT( IsInUndo(), "SplitContent only for Undo()!" ); 3915 DBG_ASSERT( nSepPos <= pNode->Len(), "Index out of range: SplitContent" ); 3916 EditPaM aPaM( pNode, nSepPos ); 3917 return ImpInsertParaBreak( aPaM ); 3918 } 3919 3920 EditPaM ImpEditEngine::ConnectContents( sal_Int32 nLeftNode, bool bBackward ) 3921 { 3922 ContentNode* pLeftNode = aEditDoc.GetObject( nLeftNode ); 3923 ContentNode* pRightNode = aEditDoc.GetObject( nLeftNode+1 ); 3924 DBG_ASSERT( pLeftNode, "Invalid left node in ConnectContents "); 3925 DBG_ASSERT( pRightNode, "Invalid right node in ConnectContents "); 3926 return ImpConnectParagraphs( pLeftNode, pRightNode, bBackward ); 3927 } 3928 3929 void ImpEditEngine::SetUpdateMode( bool bUp, EditView* pCurView, bool bForceUpdate ) 3930 { 3931 bool bChanged = ( GetUpdateMode() != bUp ); 3932 3933 // When switching from sal_True to sal_False, all selections were visible, 3934 // => paint over 3935 // the other hand, were all invisible => paint 3936 // If !bFormatted, e.g. after SetText, then if UpdateMode=sal_True 3937 // formatting is not needed immediately, probably because more text is coming. 3938 // At latest it is formatted at a Paint/CalcTextWidth. 3939 bUpdate = bUp; 3940 if ( bUpdate && ( bChanged || bForceUpdate ) ) 3941 FormatAndUpdate( pCurView ); 3942 } 3943 3944 void ImpEditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow ) 3945 { 3946 ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); 3947 DBG_ASSERT( pPPortion, "ShowParagraph: Paragraph does not exist! "); 3948 if ( pPPortion && ( pPPortion->IsVisible() != bShow ) ) 3949 { 3950 pPPortion->SetVisible( bShow ); 3951 3952 if ( !bShow ) 3953 { 3954 // Mark as deleted, so that no selection will end or begin at 3955 // this paragraph... 3956 aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pPPortion->GetNode(), nParagraph )); 3957 UpdateSelections(); 3958 // The region below will not be invalidated if UpdateMode = sal_False! 3959 // If anyway, then save as sal_False before SetVisible ! 3960 } 3961 3962 if ( bShow && ( pPPortion->IsInvalid() || !pPPortion->nHeight ) ) 3963 { 3964 if ( !GetTextRanger() ) 3965 { 3966 if ( pPPortion->IsInvalid() ) 3967 { 3968 vcl::Font aOldFont( GetRefDevice()->GetFont() ); 3969 CreateLines( nParagraph, 0 ); // 0: No TextRanger 3970 } 3971 else 3972 { 3973 CalcHeight( pPPortion ); 3974 } 3975 nCurTextHeight += pPPortion->GetHeight(); 3976 } 3977 else 3978 { 3979 nCurTextHeight = 0x7fffffff; 3980 } 3981 } 3982 3983 pPPortion->SetMustRepaint( true ); 3984 if ( GetUpdateMode() && !IsInUndo() && !GetTextRanger() ) 3985 { 3986 aInvalidRect = tools::Rectangle( Point( 0, GetParaPortions().GetYOffset( pPPortion ) ), 3987 Point( GetPaperSize().Width(), nCurTextHeight ) ); 3988 UpdateViews( GetActiveView() ); 3989 } 3990 } 3991 } 3992 3993 EditSelection ImpEditEngine::MoveParagraphs( Range aOldPositions, sal_Int32 nNewPos, EditView* pCurView ) 3994 { 3995 DBG_ASSERT( GetParaPortions().Count() != 0, "No paragraphs found: MoveParagraphs" ); 3996 if ( GetParaPortions().Count() == 0 ) 3997 return EditSelection(); 3998 aOldPositions.Justify(); 3999 4000 EditSelection aSel( ImpMoveParagraphs( aOldPositions, nNewPos ) ); 4001 4002 if ( nNewPos >= GetParaPortions().Count() ) 4003 nNewPos = GetParaPortions().Count() - 1; 4004 4005 // Where the paragraph was inserted it has to be properly redrawn: 4006 // Where the paragraph was removed it has to be properly redrawn: 4007 // ( and correspondingly in between as well...) 4008 if ( pCurView && GetUpdateMode() ) 4009 { 4010 // in this case one can redraw directly without invalidating the 4011 // Portions 4012 sal_Int32 nFirstPortion = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos ); 4013 sal_Int32 nLastPortion = std::max( static_cast<sal_Int32>(aOldPositions.Max()), nNewPos ); 4014 4015 ParaPortion* pUpperPortion = GetParaPortions().SafeGetObject( nFirstPortion ); 4016 ParaPortion* pLowerPortion = GetParaPortions().SafeGetObject( nLastPortion ); 4017 if (pUpperPortion && pLowerPortion) 4018 { 4019 aInvalidRect = tools::Rectangle(); // make empty 4020 aInvalidRect.SetLeft( 0 ); 4021 aInvalidRect.SetRight( aPaperSize.Width() ); 4022 aInvalidRect.SetTop( GetParaPortions().GetYOffset( pUpperPortion ) ); 4023 aInvalidRect.SetBottom( GetParaPortions().GetYOffset( pLowerPortion ) + pLowerPortion->GetHeight() ); 4024 4025 UpdateViews( pCurView ); 4026 } 4027 } 4028 else 4029 { 4030 // redraw from the upper invalid position 4031 sal_Int32 nFirstInvPara = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos ); 4032 InvalidateFromParagraph( nFirstInvPara ); 4033 } 4034 return aSel; 4035 } 4036 4037 void ImpEditEngine::InvalidateFromParagraph( sal_Int32 nFirstInvPara ) 4038 { 4039 // The following paragraphs are not invalidated, since ResetHeight() 4040 // => size change => all the following are re-issued anyway. 4041 ParaPortion* pTmpPortion; 4042 if ( nFirstInvPara != 0 ) 4043 { 4044 pTmpPortion = GetParaPortions()[nFirstInvPara-1]; 4045 pTmpPortion->MarkInvalid( pTmpPortion->GetNode()->Len(), 0 ); 4046 } 4047 else 4048 { 4049 pTmpPortion = GetParaPortions()[0]; 4050 pTmpPortion->MarkSelectionInvalid( 0 ); 4051 } 4052 pTmpPortion->ResetHeight(); 4053 } 4054 4055 IMPL_LINK_NOARG(ImpEditEngine, StatusTimerHdl, Timer *, void) 4056 { 4057 CallStatusHdl(); 4058 } 4059 4060 void ImpEditEngine::CallStatusHdl() 4061 { 4062 if ( aStatusHdlLink.IsSet() && bool(aStatus.GetStatusWord()) ) 4063 { 4064 // The Status has to be reset before the Call, 4065 // since other Flags might be set in the handler... 4066 EditStatus aTmpStatus( aStatus ); 4067 aStatus.Clear(); 4068 aStatusHdlLink.Call( aTmpStatus ); 4069 aStatusTimer.Stop(); // If called by hand... 4070 } 4071 } 4072 4073 ContentNode* ImpEditEngine::GetPrevVisNode( ContentNode const * pCurNode ) 4074 { 4075 const ParaPortion* pPortion = FindParaPortion( pCurNode ); 4076 DBG_ASSERT( pPortion, "GetPrevVisibleNode: No matching portion!" ); 4077 pPortion = GetPrevVisPortion( pPortion ); 4078 if ( pPortion ) 4079 return pPortion->GetNode(); 4080 return nullptr; 4081 } 4082 4083 ContentNode* ImpEditEngine::GetNextVisNode( ContentNode const * pCurNode ) 4084 { 4085 const ParaPortion* pPortion = FindParaPortion( pCurNode ); 4086 DBG_ASSERT( pPortion, "GetNextVisibleNode: No matching portion!" ); 4087 pPortion = GetNextVisPortion( pPortion ); 4088 if ( pPortion ) 4089 return pPortion->GetNode(); 4090 return nullptr; 4091 } 4092 4093 const ParaPortion* ImpEditEngine::GetPrevVisPortion( const ParaPortion* pCurPortion ) const 4094 { 4095 sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion ); 4096 DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisPortion" ); 4097 const ParaPortion* pPortion = nPara ? GetParaPortions()[--nPara] : nullptr; 4098 while ( pPortion && !pPortion->IsVisible() ) 4099 pPortion = nPara ? GetParaPortions()[--nPara] : nullptr; 4100 4101 return pPortion; 4102 } 4103 4104 const ParaPortion* ImpEditEngine::GetNextVisPortion( const ParaPortion* pCurPortion ) const 4105 { 4106 sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion ); 4107 DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisNode" ); 4108 const ParaPortion* pPortion = GetParaPortions().SafeGetObject( ++nPara ); 4109 while ( pPortion && !pPortion->IsVisible() ) 4110 pPortion = GetParaPortions().SafeGetObject( ++nPara ); 4111 4112 return pPortion; 4113 } 4114 4115 long ImpEditEngine::CalcVertLineSpacing(Point& rStartPos) const 4116 { 4117 long nTotalOccupiedHeight = 0; 4118 sal_Int32 nTotalLineCount = 0; 4119 const ParaPortionList& rParaPortions = GetParaPortions(); 4120 sal_Int32 nParaCount = rParaPortions.Count(); 4121 4122 for (sal_Int32 i = 0; i < nParaCount; ++i) 4123 { 4124 if (GetVerJustification(i) != SvxCellVerJustify::Block) 4125 // All paragraphs must have the block justification set. 4126 return 0; 4127 4128 const ParaPortion* pPortion = rParaPortions[i]; 4129 nTotalOccupiedHeight += pPortion->GetFirstLineOffset(); 4130 4131 const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL); 4132 sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) 4133 ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; 4134 4135 const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE); 4136 long nUL = GetYValue( rULItem.GetLower() ); 4137 4138 const EditLineList& rLines = pPortion->GetLines(); 4139 sal_Int32 nLineCount = rLines.Count(); 4140 nTotalLineCount += nLineCount; 4141 for (sal_Int32 j = 0; j < nLineCount; ++j) 4142 { 4143 const EditLine& rLine = rLines[j]; 4144 nTotalOccupiedHeight += rLine.GetHeight(); 4145 if (j < nLineCount-1) 4146 nTotalOccupiedHeight += nSBL; 4147 nTotalOccupiedHeight += nUL; 4148 } 4149 } 4150 4151 long nTotalSpace = IsVertical() ? aPaperSize.Width() : aPaperSize.Height(); 4152 nTotalSpace -= nTotalOccupiedHeight; 4153 if (nTotalSpace <= 0 || nTotalLineCount <= 1) 4154 return 0; 4155 4156 if (IsVertical()) 4157 { 4158 if( IsTopToBottom() ) 4159 // Shift the text to the right for the asian layout mode. 4160 rStartPos.AdjustX(nTotalSpace ); 4161 else 4162 rStartPos.AdjustX( -nTotalSpace ); 4163 } 4164 4165 return nTotalSpace / (nTotalLineCount-1); 4166 } 4167 4168 EditPaM ImpEditEngine::InsertParagraph( sal_Int32 nPara ) 4169 { 4170 EditPaM aPaM; 4171 if ( nPara != 0 ) 4172 { 4173 ContentNode* pNode = GetEditDoc().GetObject( nPara-1 ); 4174 if ( !pNode ) 4175 pNode = GetEditDoc().GetObject( GetEditDoc().Count() - 1 ); 4176 assert(pNode && "Not a single paragraph in InsertParagraph ?"); 4177 aPaM = EditPaM( pNode, pNode->Len() ); 4178 } 4179 else 4180 { 4181 ContentNode* pNode = GetEditDoc().GetObject( 0 ); 4182 aPaM = EditPaM( pNode, 0 ); 4183 } 4184 4185 return ImpInsertParaBreak( aPaM ); 4186 } 4187 4188 std::unique_ptr<EditSelection> ImpEditEngine::SelectParagraph( sal_Int32 nPara ) 4189 { 4190 std::unique_ptr<EditSelection> pSel; 4191 ContentNode* pNode = GetEditDoc().GetObject( nPara ); 4192 SAL_WARN_IF( !pNode, "editeng", "Paragraph does not exist: SelectParagraph" ); 4193 if ( pNode ) 4194 pSel.reset(new EditSelection( EditPaM( pNode, 0 ), EditPaM( pNode, pNode->Len() ) )); 4195 4196 return pSel; 4197 } 4198 4199 void ImpEditEngine::FormatAndUpdate( EditView* pCurView, bool bCalledFromUndo ) 4200 { 4201 if ( bDowning ) 4202 return ; 4203 4204 if ( IsInUndo() ) 4205 IdleFormatAndUpdate( pCurView ); 4206 else 4207 { 4208 if (bCalledFromUndo) 4209 // in order to make bullet points that have had their styles changed, redraw themselves 4210 for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) 4211 GetParaPortions()[nPortion]->MarkInvalid( 0, 0 ); 4212 FormatDoc(); 4213 UpdateViews( pCurView ); 4214 } 4215 4216 EENotify aNotify(EE_NOTIFY_PROCESSNOTIFICATIONS); 4217 GetNotifyHdl().Call(aNotify); 4218 } 4219 4220 void ImpEditEngine::SetFlatMode( bool bFlat ) 4221 { 4222 if ( bFlat != aStatus.UseCharAttribs() ) 4223 return; 4224 4225 if ( !bFlat ) 4226 aStatus.TurnOnFlags( EEControlBits::USECHARATTRIBS ); 4227 else 4228 aStatus.TurnOffFlags( EEControlBits::USECHARATTRIBS ); 4229 4230 aEditDoc.CreateDefFont( !bFlat ); 4231 4232 FormatFullDoc(); 4233 UpdateViews(); 4234 if ( pActiveView ) 4235 pActiveView->ShowCursor(); 4236 } 4237 4238 void ImpEditEngine::SetCharStretching( sal_uInt16 nX, sal_uInt16 nY ) 4239 { 4240 bool bChanged(false); 4241 if ( !IsVertical() ) 4242 { 4243 bChanged = nStretchX!=nX || nStretchY!=nY; 4244 nStretchX = nX; 4245 nStretchY = nY; 4246 } 4247 else 4248 { 4249 bChanged = nStretchX!=nY || nStretchY!=nX; 4250 nStretchX = nY; 4251 nStretchY = nX; 4252 } 4253 4254 if (bChanged && aStatus.DoStretch()) 4255 { 4256 FormatFullDoc(); 4257 // (potentially) need everything redrawn 4258 aInvalidRect=tools::Rectangle(0,0,1000000,1000000); 4259 UpdateViews( GetActiveView() ); 4260 } 4261 } 4262 4263 const SvxNumberFormat* ImpEditEngine::GetNumberFormat( const ContentNode *pNode ) const 4264 { 4265 const SvxNumberFormat *pRes = nullptr; 4266 4267 if (pNode) 4268 { 4269 // get index of paragraph 4270 sal_Int32 nPara = GetEditDoc().GetPos( pNode ); 4271 DBG_ASSERT( nPara < EE_PARA_NOT_FOUND, "node not found in array" ); 4272 if (nPara < EE_PARA_NOT_FOUND) 4273 { 4274 // the called function may be overridden by an OutlinerEditEng 4275 // object to provide 4276 // access to the SvxNumberFormat of the Outliner. 4277 // The EditEngine implementation will just return 0. 4278 pRes = pEditEngine->GetNumberFormat( nPara ); 4279 } 4280 } 4281 4282 return pRes; 4283 } 4284 4285 sal_Int32 ImpEditEngine::GetSpaceBeforeAndMinLabelWidth( 4286 const ContentNode *pNode, 4287 sal_Int32 *pnSpaceBefore, sal_Int32 *pnMinLabelWidth ) const 4288 { 4289 // nSpaceBefore matches the ODF attribute text:space-before 4290 // nMinLabelWidth matches the ODF attribute text:min-label-width 4291 4292 const SvxNumberFormat *pNumFmt = GetNumberFormat( pNode ); 4293 4294 // if no number format was found we have no Outliner or the numbering level 4295 // within the Outliner is -1 which means no number format should be applied. 4296 // Thus the default values to be returned are 0. 4297 sal_Int32 nSpaceBefore = 0; 4298 sal_Int32 nMinLabelWidth = 0; 4299 4300 if (pNumFmt) 4301 { 4302 nMinLabelWidth = -pNumFmt->GetFirstLineOffset(); 4303 nSpaceBefore = pNumFmt->GetAbsLSpace() - nMinLabelWidth; 4304 DBG_ASSERT( nMinLabelWidth >= 0, "ImpEditEngine::GetSpaceBeforeAndMinLabelWidth: min-label-width < 0 encountered" ); 4305 } 4306 if (pnSpaceBefore) 4307 *pnSpaceBefore = nSpaceBefore; 4308 if (pnMinLabelWidth) 4309 *pnMinLabelWidth = nMinLabelWidth; 4310 4311 return nSpaceBefore + nMinLabelWidth; 4312 } 4313 4314 const SvxLRSpaceItem& ImpEditEngine::GetLRSpaceItem( ContentNode* pNode ) 4315 { 4316 return pNode->GetContentAttribs().GetItem( aStatus.IsOutliner() ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE ); 4317 } 4318 4319 // select a representative text language for the digit type according to the 4320 // text numeral setting: 4321 LanguageType ImpEditEngine::ImplCalcDigitLang(LanguageType eCurLang) const 4322 { 4323 if (utl::ConfigManager::IsFuzzing()) 4324 return LANGUAGE_ENGLISH_US; 4325 4326 // #114278# Also setting up digit language from Svt options 4327 // (cannot reliably inherit the outdev's setting) 4328 if( !pCTLOptions ) 4329 pCTLOptions.reset( new SvtCTLOptions ); 4330 4331 LanguageType eLang = eCurLang; 4332 const SvtCTLOptions::TextNumerals nCTLTextNumerals = pCTLOptions->GetCTLTextNumerals(); 4333 4334 if ( SvtCTLOptions::NUMERALS_HINDI == nCTLTextNumerals ) 4335 eLang = LANGUAGE_ARABIC_SAUDI_ARABIA; 4336 else if ( SvtCTLOptions::NUMERALS_ARABIC == nCTLTextNumerals ) 4337 eLang = LANGUAGE_ENGLISH; 4338 else if ( SvtCTLOptions::NUMERALS_SYSTEM == nCTLTextNumerals ) 4339 eLang = Application::GetSettings().GetLanguageTag().getLanguageType(); 4340 4341 return eLang; 4342 } 4343 4344 OUString ImpEditEngine::convertDigits(const OUString &rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang) 4345 { 4346 OUStringBuffer aBuf(rString); 4347 for (sal_Int32 nIdx = nStt, nEnd = nStt + nLen; nIdx < nEnd; ++nIdx) 4348 { 4349 sal_Unicode cChar = aBuf[nIdx]; 4350 if (cChar >= '0' && cChar <= '9') 4351 aBuf[nIdx] = GetLocalizedChar(cChar, eDigitLang); 4352 } 4353 return aBuf.makeStringAndClear(); 4354 } 4355 4356 // Either sets the digit mode at the output device 4357 void ImpEditEngine::ImplInitDigitMode(OutputDevice* pOutDev, LanguageType eCurLang) 4358 { 4359 assert(pOutDev); //presumably there isn't any case where pOutDev should be NULL? 4360 if (pOutDev) 4361 pOutDev->SetDigitLanguage(ImplCalcDigitLang(eCurLang)); 4362 } 4363 4364 void ImpEditEngine::ImplInitLayoutMode( OutputDevice* pOutDev, sal_Int32 nPara, sal_Int32 nIndex ) 4365 { 4366 bool bCTL = false; 4367 bool bR2L = false; 4368 if ( nIndex == -1 ) 4369 { 4370 bCTL = HasScriptType( nPara, i18n::ScriptType::COMPLEX ); 4371 bR2L = IsRightToLeft( nPara ); 4372 } 4373 else 4374 { 4375 ContentNode* pNode = GetEditDoc().GetObject( nPara ); 4376 short nScriptType = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) ); 4377 bCTL = nScriptType == i18n::ScriptType::COMPLEX; 4378 // this change was discussed in issue 37190 4379 bR2L = (GetRightToLeft( nPara, nIndex + 1) % 2) != 0; 4380 // it also works for issue 55927 4381 } 4382 4383 ComplexTextLayoutFlags nLayoutMode = pOutDev->GetLayoutMode(); 4384 4385 // We always use the left position for DrawText() 4386 nLayoutMode &= ~ComplexTextLayoutFlags::BiDiRtl; 4387 4388 if ( !bCTL && !bR2L) 4389 { 4390 // No Bidi checking necessary 4391 nLayoutMode |= ComplexTextLayoutFlags::BiDiStrong; 4392 } 4393 else 4394 { 4395 // Bidi checking necessary 4396 // Don't use BIDI_STRONG, VCL must do some checks. 4397 nLayoutMode &= ~ComplexTextLayoutFlags::BiDiStrong; 4398 4399 if ( bR2L ) 4400 nLayoutMode |= ComplexTextLayoutFlags::BiDiRtl|ComplexTextLayoutFlags::TextOriginLeft; 4401 } 4402 4403 pOutDev->SetLayoutMode( nLayoutMode ); 4404 4405 // #114278# Also setting up digit language from Svt options 4406 // (cannot reliably inherit the outdev's setting) 4407 LanguageType eLang = Application::GetSettings().GetLanguageTag().getLanguageType(); 4408 ImplInitDigitMode( pOutDev, eLang ); 4409 } 4410 4411 Reference < i18n::XBreakIterator > const & ImpEditEngine::ImplGetBreakIterator() const 4412 { 4413 if ( !xBI.is() ) 4414 { 4415 Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); 4416 xBI = i18n::BreakIterator::create( xContext ); 4417 } 4418 return xBI; 4419 } 4420 4421 Reference < i18n::XExtendedInputSequenceChecker > const & ImpEditEngine::ImplGetInputSequenceChecker() const 4422 { 4423 if ( !xISC.is() ) 4424 { 4425 Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); 4426 xISC = i18n::InputSequenceChecker::create( xContext ); 4427 } 4428 return xISC; 4429 } 4430 4431 Color ImpEditEngine::GetAutoColor() const 4432 { 4433 Color aColor = GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor; 4434 4435 if ( GetBackgroundColor() != COL_AUTO ) 4436 { 4437 if ( GetBackgroundColor().IsDark() && aColor.IsDark() ) 4438 aColor = COL_WHITE; 4439 else if ( GetBackgroundColor().IsBright() && aColor.IsBright() ) 4440 aColor = COL_BLACK; 4441 } 4442 4443 return aColor; 4444 } 4445 4446 bool ImpEditEngine::ImplCalcAsianCompression(ContentNode* pNode, 4447 TextPortion* pTextPortion, sal_Int32 nStartPos, 4448 long* pDXArray, sal_uInt16 n100thPercentFromMax, 4449 bool bManipulateDXArray) 4450 { 4451 DBG_ASSERT( GetAsianCompressionMode() != CharCompressType::NONE, "ImplCalcAsianCompression - Why?" ); 4452 DBG_ASSERT( pTextPortion->GetLen(), "ImplCalcAsianCompression - Empty Portion?" ); 4453 4454 // Percent is 1/100 Percent... 4455 if ( n100thPercentFromMax == 10000 ) 4456 pTextPortion->SetExtraInfos( nullptr ); 4457 4458 bool bCompressed = false; 4459 4460 if ( GetI18NScriptType( EditPaM( pNode, nStartPos+1 ) ) == i18n::ScriptType::ASIAN ) 4461 { 4462 long nNewPortionWidth = pTextPortion->GetSize().Width(); 4463 sal_Int32 nPortionLen = pTextPortion->GetLen(); 4464 for ( sal_Int32 n = 0; n < nPortionLen; n++ ) 4465 { 4466 AsianCompressionFlags nType = GetCharTypeForCompression( pNode->GetChar( n+nStartPos ) ); 4467 4468 bool bCompressPunctuation = ( nType == AsianCompressionFlags::PunctuationLeft ) || ( nType == AsianCompressionFlags::PunctuationRight ); 4469 bool bCompressKana = ( nType == AsianCompressionFlags::Kana ) && ( GetAsianCompressionMode() == CharCompressType::PunctuationAndKana ); 4470 4471 // create Extra infos only if needed... 4472 if ( bCompressPunctuation || bCompressKana ) 4473 { 4474 if ( !pTextPortion->GetExtraInfos() ) 4475 { 4476 ExtraPortionInfo* pExtraInfos = new ExtraPortionInfo; 4477 pTextPortion->SetExtraInfos( pExtraInfos ); 4478 pExtraInfos->nOrgWidth = pTextPortion->GetSize().Width(); 4479 pExtraInfos->nAsianCompressionTypes = AsianCompressionFlags::Normal; 4480 } 4481 pTextPortion->GetExtraInfos()->nMaxCompression100thPercent = n100thPercentFromMax; 4482 pTextPortion->GetExtraInfos()->nAsianCompressionTypes |= nType; 4483 4484 long nOldCharWidth; 4485 if ( (n+1) < nPortionLen ) 4486 { 4487 nOldCharWidth = pDXArray[n]; 4488 } 4489 else 4490 { 4491 if ( bManipulateDXArray ) 4492 nOldCharWidth = nNewPortionWidth - pTextPortion->GetExtraInfos()->nPortionOffsetX; 4493 else 4494 nOldCharWidth = pTextPortion->GetExtraInfos()->nOrgWidth; 4495 } 4496 nOldCharWidth -= ( n ? pDXArray[n-1] : 0 ); 4497 4498 long nCompress = 0; 4499 4500 if ( bCompressPunctuation ) 4501 { 4502 nCompress = nOldCharWidth / 2; 4503 } 4504 else // Kana 4505 { 4506 nCompress = nOldCharWidth / 10; 4507 } 4508 4509 if ( n100thPercentFromMax != 10000 ) 4510 { 4511 nCompress *= n100thPercentFromMax; 4512 nCompress /= 10000; 4513 } 4514 4515 if ( nCompress ) 4516 { 4517 bCompressed = true; 4518 nNewPortionWidth -= nCompress; 4519 pTextPortion->GetExtraInfos()->bCompressed = true; 4520 4521 4522 // Special handling for rightpunctuation: For the 'compression' we must 4523 // start the output before the normal char position... 4524 if ( bManipulateDXArray && ( pTextPortion->GetLen() > 1 ) ) 4525 { 4526 if ( !pTextPortion->GetExtraInfos()->pOrgDXArray ) 4527 pTextPortion->GetExtraInfos()->SaveOrgDXArray( pDXArray, pTextPortion->GetLen()-1 ); 4528 4529 if ( nType == AsianCompressionFlags::PunctuationRight ) 4530 { 4531 // If it's the first char, I must handle it in Paint()... 4532 if ( n ) 4533 { 4534 // -1: No entry for the last character 4535 for ( sal_Int32 i = n-1; i < (nPortionLen-1); i++ ) 4536 pDXArray[i] -= nCompress; 4537 } 4538 else 4539 { 4540 pTextPortion->GetExtraInfos()->bFirstCharIsRightPunktuation = true; 4541 pTextPortion->GetExtraInfos()->nPortionOffsetX = -nCompress; 4542 } 4543 } 4544 else 4545 { 4546 // -1: No entry for the last character 4547 for ( sal_Int32 i = n; i < (nPortionLen-1); i++ ) 4548 pDXArray[i] -= nCompress; 4549 } 4550 } 4551 } 4552 } 4553 } 4554 4555 if ( bCompressed && ( n100thPercentFromMax == 10000 ) ) 4556 pTextPortion->GetExtraInfos()->nWidthFullCompression = nNewPortionWidth; 4557 4558 pTextPortion->GetSize().setWidth( nNewPortionWidth ); 4559 4560 if ( pTextPortion->GetExtraInfos() && ( n100thPercentFromMax != 10000 ) ) 4561 { 4562 // Maybe rounding errors in nNewPortionWidth, assure that width not bigger than expected 4563 long nShrink = pTextPortion->GetExtraInfos()->nOrgWidth - pTextPortion->GetExtraInfos()->nWidthFullCompression; 4564 nShrink *= n100thPercentFromMax; 4565 nShrink /= 10000; 4566 long nNewWidth = pTextPortion->GetExtraInfos()->nOrgWidth - nShrink; 4567 if ( nNewWidth < pTextPortion->GetSize().Width() ) 4568 pTextPortion->GetSize().setWidth( nNewWidth ); 4569 } 4570 } 4571 return bCompressed; 4572 } 4573 4574 4575 void ImpEditEngine::ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* pParaPortion, long nRemainingWidth ) 4576 { 4577 bool bFoundCompressedPortion = false; 4578 long nCompressed = 0; 4579 std::vector<TextPortion*> aCompressedPortions; 4580 4581 sal_Int32 nPortion = pLine->GetEndPortion(); 4582 TextPortion* pTP = &pParaPortion->GetTextPortions()[ nPortion ]; 4583 while ( pTP && ( pTP->GetKind() == PortionKind::TEXT ) ) 4584 { 4585 if ( pTP->GetExtraInfos() && pTP->GetExtraInfos()->bCompressed ) 4586 { 4587 bFoundCompressedPortion = true; 4588 nCompressed += pTP->GetExtraInfos()->nOrgWidth - pTP->GetSize().Width(); 4589 aCompressedPortions.push_back(pTP); 4590 } 4591 pTP = ( nPortion > pLine->GetStartPortion() ) ? &pParaPortion->GetTextPortions()[ --nPortion ] : nullptr; 4592 } 4593 4594 if ( bFoundCompressedPortion ) 4595 { 4596 long nCompressPercent = 0; 4597 if ( nCompressed > nRemainingWidth ) 4598 { 4599 nCompressPercent = nCompressed - nRemainingWidth; 4600 DBG_ASSERT( nCompressPercent < 200000, "ImplExpandCompressedPortions - Overflow!" ); 4601 nCompressPercent *= 10000; 4602 nCompressPercent /= nCompressed; 4603 } 4604 4605 for (TextPortion* pTP2 : aCompressedPortions) 4606 { 4607 pTP = pTP2; 4608 pTP->GetExtraInfos()->bCompressed = false; 4609 pTP->GetSize().setWidth( pTP->GetExtraInfos()->nOrgWidth ); 4610 if ( nCompressPercent ) 4611 { 4612 sal_Int32 nTxtPortion = pParaPortion->GetTextPortions().GetPos( pTP ); 4613 sal_Int32 nTxtPortionStart = pParaPortion->GetTextPortions().GetStartPos( nTxtPortion ); 4614 DBG_ASSERT( nTxtPortionStart >= pLine->GetStart(), "Portion doesn't belong to the line!!!" ); 4615 long* pDXArray = pLine->GetCharPosArray().data() + (nTxtPortionStart - pLine->GetStart()); 4616 if ( pTP->GetExtraInfos()->pOrgDXArray ) 4617 memcpy( pDXArray, pTP->GetExtraInfos()->pOrgDXArray.get(), (pTP->GetLen()-1)*sizeof(sal_Int32) ); 4618 ImplCalcAsianCompression( pParaPortion->GetNode(), pTP, nTxtPortionStart, pDXArray, static_cast<sal_uInt16>(nCompressPercent), true ); 4619 } 4620 } 4621 } 4622 } 4623 4624 void ImpEditEngine::ImplUpdateOverflowingParaNum(sal_uInt32 nPaperHeight) 4625 { 4626 sal_uInt32 nY = 0; 4627 sal_uInt32 nPH; 4628 4629 for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) { 4630 ParaPortion* pPara = GetParaPortions()[nPara]; 4631 nPH = pPara->GetHeight(); 4632 nY += nPH; 4633 if ( nY > nPaperHeight /*nCurTextHeight*/ ) // found first paragraph overflowing 4634 { 4635 mnOverflowingPara = nPara; 4636 SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing #Para#: " << nPara); 4637 ImplUpdateOverflowingLineNum( nPaperHeight, nPara, nY-nPH); 4638 return; 4639 } 4640 } 4641 } 4642 4643 void ImpEditEngine::ImplUpdateOverflowingLineNum(sal_uInt32 nPaperHeight, 4644 sal_uInt32 nOverflowingPara, 4645 sal_uInt32 nHeightBeforeOverflowingPara) 4646 { 4647 sal_uInt32 nY = nHeightBeforeOverflowingPara; 4648 sal_uInt32 nLH; 4649 4650 ParaPortion *pPara = GetParaPortions()[nOverflowingPara]; 4651 4652 // Like UpdateOverflowingParaNum but for each line in the first 4653 // overflowing paragraph. 4654 for ( sal_Int32 nLine = 0; nLine < pPara->GetLines().Count(); nLine++ ) { 4655 // XXX: We must use a reference here because the copy constructor resets the height 4656 EditLine &aLine = pPara->GetLines()[nLine]; 4657 nLH = aLine.GetHeight(); 4658 nY += nLH; 4659 4660 // Debugging output 4661 if (nLine == 0) { 4662 SAL_INFO("editeng.chaining", "[CHAINING] First line has height " << nLH); 4663 } 4664 4665 if ( nY > nPaperHeight ) // found first line overflowing 4666 { 4667 mnOverflowingLine = nLine; 4668 SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing -Line- to: " << nLine); 4669 return; 4670 } 4671 } 4672 4673 assert(false && "You should never get here"); 4674 } 4675 4676 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 4677
