1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 11 * Licensed to the Apache Software Foundation (ASF) under one or more 12 * contributor license agreements. See the NOTICE file distributed 13 * with this work for additional information regarding copyright 14 * ownership. The ASF licenses this file to you under the Apache 15 * License, Version 2.0 (the "License"); you may not use this file 16 * except in compliance with the License. You may obtain a copy of 17 * the License at http://www.apache.org/licenses/LICENSE-2.0 . 18 */ 19 20 #include <hintids.hxx> 21 22 #include <memory> 23 #include <com/sun/star/i18n/ScriptType.hpp> 24 #include <editeng/lspcitem.hxx> 25 #include <dcontact.hxx> 26 #include <txtflcnt.hxx> 27 #include <txtftn.hxx> 28 #include <flyfrms.hxx> 29 #include <fmtflcnt.hxx> 30 #include <fmtftn.hxx> 31 #include <ftninfo.hxx> 32 #include <charfmt.hxx> 33 #include <editeng/charrotateitem.hxx> 34 #include <layfrm.hxx> 35 #include <viewsh.hxx> 36 #include <viewopt.hxx> 37 #include <paratr.hxx> 38 #include "itrform2.hxx" 39 #include "porrst.hxx" 40 #include "portab.hxx" 41 #include "porfly.hxx" 42 #include "portox.hxx" 43 #include "porref.hxx" 44 #include "porfld.hxx" 45 #include "porftn.hxx" 46 #include "porhyph.hxx" 47 #include "pordrop.hxx" 48 #include "guess.hxx" 49 #include <blink.hxx> 50 #include <ftnfrm.hxx> 51 #include "redlnitr.hxx" 52 #include <pagefrm.hxx> 53 #include <pagedesc.hxx> 54 #include <tgrditem.hxx> 55 #include <doc.hxx> 56 #include "pormulti.hxx" 57 #include <unotools/charclass.hxx> 58 #include <xmloff/odffields.hxx> 59 #include <IDocumentSettingAccess.hxx> 60 #include <IMark.hxx> 61 #include <IDocumentMarkAccess.hxx> 62 #include <svl/zforlist.hxx> 63 64 #include <vector> 65 66 using namespace ::com::sun::star; 67 68 namespace { 69 //! Calculates and sets optimal repaint offset for the current line 70 long lcl_CalcOptRepaint( SwTextFormatter &rThis, 71 SwLineLayout const &rCurr, 72 TextFrameIndex nOldLineEnd, 73 const std::vector<long> &rFlyStarts ); 74 //! Determine if we need to build hidden portions 75 bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex &rPos); 76 77 // Check whether the two font has the same border 78 bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond); 79 } 80 81 static void ClearFly( SwTextFormatInfo &rInf ) 82 { 83 delete rInf.GetFly(); 84 rInf.SetFly(nullptr); 85 } 86 87 void SwTextFormatter::CtorInitTextFormatter( SwTextFrame *pNewFrame, SwTextFormatInfo *pNewInf ) 88 { 89 CtorInitTextPainter( pNewFrame, pNewInf ); 90 m_pInf = pNewInf; 91 pDropFormat = GetInfo().GetDropFormat(); 92 pMulti = nullptr; 93 94 bOnceMore = false; 95 bFlyInCntBase = false; 96 bTruncLines = false; 97 nCntEndHyph = 0; 98 nCntMidHyph = 0; 99 nLeftScanIdx = TextFrameIndex(COMPLETE_STRING); 100 nRightScanIdx = TextFrameIndex(0); 101 m_pByEndIter.reset(); 102 m_pFirstOfBorderMerge = nullptr; 103 104 if (m_nStart > TextFrameIndex(GetInfo().GetText().getLength())) 105 { 106 OSL_ENSURE( false, "+SwTextFormatter::CTOR: bad offset" ); 107 m_nStart = TextFrameIndex(GetInfo().GetText().getLength()); 108 } 109 110 } 111 112 SwTextFormatter::~SwTextFormatter() 113 { 114 // Extremely unlikely, but still possible 115 // e.g.: field splits up, widows start to matter 116 if( GetInfo().GetRest() ) 117 { 118 delete GetInfo().GetRest(); 119 GetInfo().SetRest(nullptr); 120 } 121 } 122 123 void SwTextFormatter::Insert( SwLineLayout *pLay ) 124 { 125 // Insert BEHIND the current element 126 if ( m_pCurr ) 127 { 128 pLay->SetNext( m_pCurr->GetNext() ); 129 m_pCurr->SetNext( pLay ); 130 } 131 else 132 m_pCurr = pLay; 133 } 134 135 sal_uInt16 SwTextFormatter::GetFrameRstHeight() const 136 { 137 // We want the rest height relative to the page. 138 // If we're in a table, then pFrame->GetUpper() is not the page. 139 140 // GetFrameRstHeight() is being called with Footnote. 141 // Wrong: const SwFrame *pUpper = pFrame->GetUpper(); 142 const SwFrame *pPage = static_cast<const SwFrame*>(m_pFrame->FindPageFrame()); 143 const SwTwips nHeight = pPage->getFrameArea().Top() 144 + pPage->getFramePrintArea().Top() 145 + pPage->getFramePrintArea().Height() - Y(); 146 if( 0 > nHeight ) 147 return m_pCurr->Height(); 148 else 149 return sal_uInt16( nHeight ); 150 } 151 152 SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf ) 153 { 154 // Save values and initialize rInf 155 SwLinePortion *pUnderflow = rInf.GetUnderflow(); 156 if( !pUnderflow ) 157 return nullptr; 158 159 // We format backwards, i.e. attribute changes can happen the next 160 // line again. 161 // Can be seen in 8081.sdw, if you enter text in the first line 162 163 TextFrameIndex const nSoftHyphPos = rInf.GetSoftHyphPos(); 164 TextFrameIndex const nUnderScorePos = rInf.GetUnderScorePos(); 165 166 // Save flys and set to 0, or else segmentation fault 167 // Not ClearFly(rInf) ! 168 SwFlyPortion *pFly = rInf.GetFly(); 169 rInf.SetFly( nullptr ); 170 171 FeedInf( rInf ); 172 rInf.SetLast( m_pCurr ); 173 // pUnderflow does not need to be deleted, because it will drown in the following 174 // Truncate() 175 rInf.SetUnderflow(nullptr); 176 rInf.SetSoftHyphPos( nSoftHyphPos ); 177 rInf.SetUnderScorePos( nUnderScorePos ); 178 rInf.SetPaintOfst( GetLeftMargin() ); 179 180 // We look for the portion with the under-flow position 181 SwLinePortion *pPor = m_pCurr->GetFirstPortion(); 182 if( pPor != pUnderflow ) 183 { 184 // pPrev will be the last portion before pUnderflow, 185 // which still has a real width. 186 // Exception: SoftHyphPortion must not be forgotten, of course! 187 // Although they don't have a width. 188 SwLinePortion *pTmpPrev = pPor; 189 while( pPor && pPor != pUnderflow ) 190 { 191 if( !pPor->IsKernPortion() && 192 ( pPor->Width() || pPor->IsSoftHyphPortion() ) ) 193 { 194 while( pTmpPrev != pPor ) 195 { 196 pTmpPrev->Move( rInf ); 197 rInf.SetLast( pTmpPrev ); 198 pTmpPrev = pTmpPrev->GetNextPortion(); 199 OSL_ENSURE( pTmpPrev, "Underflow: losing control!" ); 200 }; 201 } 202 pPor = pPor->GetNextPortion(); 203 } 204 pPor = pTmpPrev; 205 if( pPor && // Skip flys and initials when underflow. 206 ( pPor->IsFlyPortion() || pPor->IsDropPortion() || 207 pPor->IsFlyCntPortion() ) ) 208 { 209 pPor->Move( rInf ); 210 rInf.SetLast( pPor ); 211 rInf.SetStopUnderflow( true ); 212 pPor = pUnderflow; 213 } 214 } 215 216 // What? The under-flow portion is not in the portion chain? 217 OSL_ENSURE( pPor, "SwTextFormatter::Underflow: overflow but underflow" ); 218 219 // Snapshot 220 if ( pPor==rInf.GetLast() ) 221 { 222 // We end up here, if the portion triggering the under-flow 223 // spans over the whole line. E.g. if a word spans across 224 // multiple lines and flows into a fly in the second line. 225 rInf.SetFly( pFly ); 226 pPor->Truncate(); 227 return pPor; // Is that enough? 228 } 229 // End the snapshot 230 231 // X + Width == 0 with SoftHyph > Line?! 232 if( !pPor || !(rInf.X() + pPor->Width()) ) 233 { 234 delete pFly; 235 return nullptr; 236 } 237 238 // Preparing for Format() 239 // We need to chip off the chain behind pLast, because we Insert after the Format() 240 SeekAndChg( rInf ); 241 242 // line width is adjusted, so that pPor does not fit to current 243 // line anymore 244 rInf.Width( static_cast<sal_uInt16>(rInf.X() + (pPor->Width() ? pPor->Width() - 1 : 0)) ); 245 rInf.SetLen( pPor->GetLen() ); 246 rInf.SetFull( false ); 247 if( pFly ) 248 { 249 // We need to recalculate the FlyPortion due to the following reason: 250 // If the base line is lowered by a big font in the middle of the line, 251 // causing overlapping with a fly, the FlyPortion has a wrong size/fixed 252 // size. 253 rInf.SetFly( pFly ); 254 CalcFlyWidth( rInf ); 255 } 256 rInf.GetLast()->SetNextPortion(nullptr); 257 258 // The SwLineLayout is an exception to this, which splits at the first 259 // portion change. 260 // Here only the other way around: 261 if( rInf.GetLast() == m_pCurr ) 262 { 263 if( pPor->InTextGrp() && !pPor->InExpGrp() ) 264 { 265 const PortionType nOldWhich = m_pCurr->GetWhichPor(); 266 *static_cast<SwLinePortion*>(m_pCurr) = *pPor; 267 m_pCurr->SetNextPortion( pPor->GetNextPortion() ); 268 m_pCurr->SetWhichPor( nOldWhich ); 269 pPor->SetNextPortion( nullptr ); 270 delete pPor; 271 pPor = m_pCurr; 272 } 273 } 274 275 // Make sure that m_pFirstOfBorderMerge does not point to a portion which 276 // will be deleted by Truncate() below. 277 SwLinePortion* pNext = pPor->GetNextPortion(); 278 while (pNext) 279 { 280 if (pNext == m_pFirstOfBorderMerge) 281 { 282 m_pFirstOfBorderMerge = nullptr; 283 break; 284 } 285 pNext = pNext->GetNextPortion(); 286 } 287 pPor->Truncate(); 288 SwLinePortion *const pRest( rInf.GetRest() ); 289 if (pRest && pRest->InFieldGrp() && 290 static_cast<SwFieldPortion*>(pRest)->IsNoLength()) 291 { 292 // HACK: decrement again, so we pick up the suffix in next line! 293 m_pByEndIter->PrevAttr(); 294 } 295 delete pRest; 296 rInf.SetRest(nullptr); 297 return pPor; 298 } 299 300 void SwTextFormatter::InsertPortion( SwTextFormatInfo &rInf, 301 SwLinePortion *pPor ) 302 { 303 SwLinePortion *pLast = nullptr; 304 // The new portion is inserted, but everything's different for 305 // LineLayout... 306 if( pPor == m_pCurr ) 307 { 308 if ( m_pCurr->GetNextPortion() ) 309 { 310 pLast = pPor; 311 pPor = m_pCurr->GetNextPortion(); 312 } 313 314 // i#112181 - Prevent footnote anchor being wrapped to next line 315 // without preceding word 316 rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() ); 317 } 318 else 319 { 320 pLast = rInf.GetLast(); 321 if( pLast->GetNextPortion() ) 322 { 323 while( pLast->GetNextPortion() ) 324 pLast = pLast->GetNextPortion(); 325 rInf.SetLast( pLast ); 326 } 327 pLast->Insert( pPor ); 328 329 rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() ); 330 331 // Adjust maxima 332 if( m_pCurr->Height() < pPor->Height() ) 333 m_pCurr->Height( pPor->Height() ); 334 if( m_pCurr->GetAscent() < pPor->GetAscent() ) 335 m_pCurr->SetAscent( pPor->GetAscent() ); 336 } 337 338 // Sometimes chains are constructed (e.g. by hyphenate) 339 rInf.SetLast( pPor ); 340 while( pPor ) 341 { 342 if (!pPor->IsDropPortion()) 343 MergeCharacterBorder(*pPor, pLast, rInf); 344 345 pPor->Move( rInf ); 346 rInf.SetLast( pPor ); 347 pLast = pPor; 348 pPor = pPor->GetNextPortion(); 349 } 350 } 351 352 void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) 353 { 354 OSL_ENSURE( rInf.GetText().getLength() < COMPLETE_STRING, 355 "SwTextFormatter::BuildPortions: bad text length in info" ); 356 357 rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() ); 358 359 // First NewTextPortion() decides whether pCurr ends up in pPor. 360 // We need to make sure that the font is being set in any case. 361 // This is done automatically in CalcAscent. 362 rInf.SetLast( m_pCurr ); 363 rInf.ForcedLeftMargin( 0 ); 364 365 OSL_ENSURE( m_pCurr->FindLastPortion() == m_pCurr, "pLast supposed to equal pCurr" ); 366 367 if( !m_pCurr->GetAscent() && !m_pCurr->Height() ) 368 CalcAscent( rInf, m_pCurr ); 369 370 SeekAndChg( rInf ); 371 372 // Width() is shortened in CalcFlyWidth if we have a FlyPortion 373 OSL_ENSURE( !rInf.X() || pMulti, "SwTextFormatter::BuildPortion X=0?" ); 374 CalcFlyWidth( rInf ); 375 SwFlyPortion *pFly = rInf.GetFly(); 376 if( pFly ) 377 { 378 if ( 0 < pFly->GetFix() ) 379 ClearFly( rInf ); 380 else 381 rInf.SetFull(true); 382 } 383 384 SwLinePortion *pPor = NewPortion( rInf ); 385 386 // Asian grid stuff 387 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); 388 const bool bHasGrid = pGrid && rInf.SnapToGrid() && 389 GRID_LINES_CHARS == pGrid->GetGridType(); 390 391 392 const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc(); 393 const sal_uInt16 nGridWidth = bHasGrid ? GetGridWidth(*pGrid, rDoc) : 0; 394 395 // used for grid mode only: 396 // the pointer is stored, because after formatting of non-asian text, 397 // the width of the kerning portion has to be adjusted 398 // Inserting a SwKernPortion before a SwTabPortion isn't necessary 399 // and will break the SwTabPortion. 400 SwKernPortion* pGridKernPortion = nullptr; 401 402 bool bFull = false; 403 SwTwips nUnderLineStart = 0; 404 rInf.Y( Y() ); 405 406 while( pPor && !rInf.IsStop() ) 407 { 408 OSL_ENSURE(rInf.GetLen() < TextFrameIndex(COMPLETE_STRING) && 409 rInf.GetIdx() <= TextFrameIndex(rInf.GetText().getLength()), 410 "SwTextFormatter::BuildPortions: bad length in info" ); 411 412 // We have to check the script for fields in order to set the 413 // correct nActual value for the font. 414 if( pPor->InFieldGrp() ) 415 static_cast<SwFieldPortion*>(pPor)->CheckScript( rInf ); 416 417 if( ! bHasGrid && rInf.HasScriptSpace() && 418 rInf.GetLast() && rInf.GetLast()->InTextGrp() && 419 rInf.GetLast()->Width() && !rInf.GetLast()->InNumberGrp() ) 420 { 421 SwFontScript nNxtActual = rInf.GetFont()->GetActual(); 422 SwFontScript nLstActual = nNxtActual; 423 sal_uInt16 nLstHeight = static_cast<sal_uInt16>(rInf.GetFont()->GetHeight()); 424 bool bAllowBehind = false; 425 const CharClass& rCC = GetAppCharClass(); 426 427 // are there any punctuation characters on both sides 428 // of the kerning portion? 429 if ( pPor->InFieldGrp() ) 430 { 431 OUString aAltText; 432 if ( static_cast<SwFieldPortion*>(pPor)->GetExpText( rInf, aAltText ) && 433 !aAltText.isEmpty() ) 434 { 435 bAllowBehind = rCC.isLetterNumeric( aAltText, 0 ); 436 437 const SwFont* pTmpFnt = static_cast<SwFieldPortion*>(pPor)->GetFont(); 438 if ( pTmpFnt ) 439 nNxtActual = pTmpFnt->GetActual(); 440 } 441 } 442 else 443 { 444 const OUString& rText = rInf.GetText(); 445 sal_Int32 nIdx = sal_Int32(rInf.GetIdx()); 446 bAllowBehind = nIdx < rText.getLength() && rCC.isLetterNumeric(rText, nIdx); 447 } 448 449 const SwLinePortion* pLast = rInf.GetLast(); 450 if ( bAllowBehind && pLast ) 451 { 452 bool bAllowBefore = false; 453 454 if ( pLast->InFieldGrp() ) 455 { 456 OUString aAltText; 457 if ( static_cast<const SwFieldPortion*>(pLast)->GetExpText( rInf, aAltText ) && 458 !aAltText.isEmpty() ) 459 { 460 bAllowBefore = rCC.isLetterNumeric( aAltText, aAltText.getLength() - 1 ); 461 462 const SwFont* pTmpFnt = static_cast<const SwFieldPortion*>(pLast)->GetFont(); 463 if ( pTmpFnt ) 464 { 465 nLstActual = pTmpFnt->GetActual(); 466 nLstHeight = static_cast<sal_uInt16>(pTmpFnt->GetHeight()); 467 } 468 } 469 } 470 else if ( rInf.GetIdx() ) 471 { 472 bAllowBefore = rCC.isLetterNumeric(rInf.GetText(), sal_Int32(rInf.GetIdx()) - 1); 473 // Note: ScriptType returns values in [1,4] 474 if ( bAllowBefore ) 475 nLstActual = SwFontScript(m_pScriptInfo->ScriptType(rInf.GetIdx() - TextFrameIndex(1)) - 1); 476 } 477 478 nLstHeight /= 5; 479 // does the kerning portion still fit into the line? 480 if( bAllowBefore && ( nLstActual != nNxtActual ) && 481 nLstHeight && rInf.X() + nLstHeight <= rInf.Width() && 482 ! pPor->InTabGrp() ) 483 { 484 SwKernPortion* pKrn = 485 new SwKernPortion( *rInf.GetLast(), nLstHeight, 486 pLast->InFieldGrp() && pPor->InFieldGrp() ); 487 rInf.GetLast()->SetNextPortion( nullptr ); 488 InsertPortion( rInf, pKrn ); 489 } 490 } 491 } 492 else if ( bHasGrid && pGrid->IsSnapToChars() && ! pGridKernPortion && ! pMulti && ! pPor->InTabGrp() ) 493 { 494 // insert a grid kerning portion 495 pGridKernPortion = pPor->IsKernPortion() ? 496 static_cast<SwKernPortion*>(pPor) : 497 new SwKernPortion( *m_pCurr ); 498 499 // if we have a new GridKernPortion, we initially calculate 500 // its size so that its ends on the grid 501 const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame(); 502 const SwLayoutFrame* pBody = pPageFrame->FindBodyCont(); 503 SwRectFnSet aRectFnSet(pPageFrame); 504 505 const long nGridOrigin = pBody ? 506 aRectFnSet.GetPrtLeft(*pBody) : 507 aRectFnSet.GetPrtLeft(*pPageFrame); 508 509 SwTwips nStartX = rInf.X() + GetLeftMargin(); 510 if ( aRectFnSet.IsVert() ) 511 { 512 Point aPoint( nStartX, 0 ); 513 m_pFrame->SwitchHorizontalToVertical( aPoint ); 514 nStartX = aPoint.Y(); 515 } 516 517 const SwTwips nOfst = nStartX - nGridOrigin; 518 if ( nOfst ) 519 { 520 const sal_uLong i = ( nOfst > 0 ) ? 521 ( ( nOfst - 1 ) / nGridWidth + 1 ) : 522 0; 523 const SwTwips nKernWidth = i * nGridWidth - nOfst; 524 const SwTwips nRestWidth = rInf.Width() - rInf.X(); 525 526 if ( nKernWidth <= nRestWidth ) 527 pGridKernPortion->Width( static_cast<sal_uInt16>(nKernWidth) ); 528 } 529 530 if ( pGridKernPortion != pPor ) 531 InsertPortion( rInf, pGridKernPortion ); 532 } 533 534 if( pPor->IsDropPortion() ) 535 MergeCharacterBorder(*static_cast<SwDropPortion*>(pPor)); 536 537 // the multi-portion has its own format function 538 if( pPor->IsMultiPortion() && ( !pMulti || pMulti->IsBidi() ) ) 539 bFull = BuildMultiPortion( rInf, *static_cast<SwMultiPortion*>(pPor) ); 540 else 541 bFull = pPor->Format( rInf ); 542 543 if( rInf.IsRuby() && !rInf.GetRest() ) 544 bFull = true; 545 546 // if we are underlined, we store the beginning of this underlined 547 // segment for repaint optimization 548 if ( LINESTYLE_NONE != m_pFont->GetUnderline() && ! nUnderLineStart ) 549 nUnderLineStart = GetLeftMargin() + rInf.X(); 550 551 if ( pPor->IsFlyPortion() ) 552 m_pCurr->SetFly( true ); 553 // some special cases, where we have to take care for the repaint 554 // offset: 555 // 1. Underlined portions due to special underline feature 556 // 2. Right Tab 557 // 3. BidiPortions 558 // 4. other Multiportions 559 // 5. DropCaps 560 // 6. Grid Mode 561 else if ( ( ! rInf.GetPaintOfst() || nUnderLineStart < rInf.GetPaintOfst() ) && 562 // 1. Underlined portions 563 nUnderLineStart && 564 // reformat is at end of an underlined portion and next portion 565 // is not underlined 566 ( ( rInf.GetReformatStart() == rInf.GetIdx() && 567 LINESTYLE_NONE == m_pFont->GetUnderline() 568 ) || 569 // reformat is inside portion and portion is underlined 570 ( rInf.GetReformatStart() >= rInf.GetIdx() && 571 rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() && 572 LINESTYLE_NONE != m_pFont->GetUnderline() ) ) ) 573 rInf.SetPaintOfst( nUnderLineStart ); 574 else if ( ! rInf.GetPaintOfst() && 575 // 2. Right Tab 576 ( ( pPor->InTabGrp() && !pPor->IsTabLeftPortion() ) || 577 // 3. BidiPortions 578 ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() ) || 579 // 4. Multi Portion and 5. Drop Caps 580 ( ( pPor->IsDropPortion() || pPor->IsMultiPortion() ) && 581 rInf.GetReformatStart() >= rInf.GetIdx() && 582 rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() ) 583 // 6. Grid Mode 584 || ( bHasGrid && SwFontScript::CJK != m_pFont->GetActual() ) 585 ) 586 ) 587 // we store the beginning of the critical portion as our 588 // paint offset 589 rInf.SetPaintOfst( GetLeftMargin() + rInf.X() ); 590 591 // under one of these conditions we are allowed to delete the 592 // start of the underline portion 593 if ( IsUnderlineBreak( *pPor, *m_pFont ) ) 594 nUnderLineStart = 0; 595 596 if( pPor->IsFlyCntPortion() || ( pPor->IsMultiPortion() && 597 static_cast<SwMultiPortion*>(pPor)->HasFlyInContent() ) ) 598 SetFlyInCntBase(); 599 // bUnderflow needs to be reset or we wrap again at the next softhyphen 600 if ( !bFull ) 601 { 602 rInf.ClrUnderflow(); 603 if( ! bHasGrid && rInf.HasScriptSpace() && pPor->InTextGrp() && 604 pPor->GetLen() && !pPor->InFieldGrp() ) 605 { 606 // The distance between two different scripts is set 607 // to 20% of the fontheight. 608 TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen(); 609 if (nTmp == m_pScriptInfo->NextScriptChg(nTmp - TextFrameIndex(1)) && 610 nTmp != TextFrameIndex(rInf.GetText().getLength()) && 611 (m_pScriptInfo->ScriptType(nTmp - TextFrameIndex(1)) == css::i18n::ScriptType::ASIAN || 612 m_pScriptInfo->ScriptType(nTmp) == css::i18n::ScriptType::ASIAN) ) 613 { 614 const sal_uInt16 nDist = static_cast<sal_uInt16>(rInf.GetFont()->GetHeight()/5); 615 616 if( nDist ) 617 { 618 // we do not want a kerning portion if any end 619 // would be a punctuation character 620 const CharClass& rCC = GetAppCharClass(); 621 if (rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp) - 1) 622 && rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp))) 623 { 624 // does the kerning portion still fit into the line? 625 if ( rInf.X() + pPor->Width() + nDist <= rInf.Width() ) 626 new SwKernPortion( *pPor, nDist ); 627 else 628 bFull = true; 629 } 630 } 631 } 632 } 633 } 634 635 if ( bHasGrid && pGrid->IsSnapToChars() && pPor != pGridKernPortion && ! pMulti && ! pPor->InTabGrp() ) 636 { 637 TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen(); 638 const SwTwips nRestWidth = rInf.Width() - rInf.X() - pPor->Width(); 639 640 const SwFontScript nCurrScript = m_pFont->GetActual(); // pScriptInfo->ScriptType( rInf.GetIdx() ); 641 const SwFontScript nNextScript = 642 nTmp >= TextFrameIndex(rInf.GetText().getLength()) 643 ? SwFontScript::CJK 644 : m_pScriptInfo->WhichFont(nTmp); 645 646 // snap non-asian text to grid if next portion is ASIAN or 647 // there are no more portions in this line 648 // be careful when handling an underflow event: the gridkernportion 649 // could have been deleted 650 if ( nRestWidth > 0 && SwFontScript::CJK != nCurrScript && 651 ! rInf.IsUnderflow() && ( bFull || SwFontScript::CJK == nNextScript ) ) 652 { 653 OSL_ENSURE( pGridKernPortion, "No GridKernPortion available" ); 654 655 // calculate size 656 SwLinePortion* pTmpPor = pGridKernPortion->GetNextPortion(); 657 sal_uInt16 nSumWidth = pPor->Width(); 658 while ( pTmpPor ) 659 { 660 nSumWidth = nSumWidth + pTmpPor->Width(); 661 pTmpPor = pTmpPor->GetNextPortion(); 662 } 663 664 const SwTwips i = nSumWidth ? 665 ( nSumWidth - 1 ) / nGridWidth + 1 : 666 0; 667 const SwTwips nTmpWidth = i * nGridWidth; 668 const SwTwips nKernWidth = std::min(nTmpWidth - nSumWidth, nRestWidth); 669 const sal_uInt16 nKernWidth_1 = static_cast<sal_uInt16>(nKernWidth / 2); 670 671 OSL_ENSURE( nKernWidth <= nRestWidth, 672 "Not enough space left for adjusting non-asian text in grid mode" ); 673 674 pGridKernPortion->Width( pGridKernPortion->Width() + nKernWidth_1 ); 675 rInf.X( rInf.X() + nKernWidth_1 ); 676 677 if ( ! bFull ) 678 new SwKernPortion( *pPor, static_cast<short>(nKernWidth - nKernWidth_1), 679 false, true ); 680 681 pGridKernPortion = nullptr; 682 } 683 else if ( pPor->IsMultiPortion() || pPor->InFixMargGrp() || 684 pPor->IsFlyCntPortion() || pPor->InNumberGrp() || 685 pPor->InFieldGrp() || nCurrScript != nNextScript ) 686 // next portion should snap to grid 687 pGridKernPortion = nullptr; 688 } 689 690 rInf.SetFull( bFull ); 691 692 // Restportions from fields with multiple lines don't yet have the right ascent 693 if ( !pPor->GetLen() && !pPor->IsFlyPortion() 694 && !pPor->IsGrfNumPortion() && ! pPor->InNumberGrp() 695 && !pPor->IsMultiPortion() ) 696 CalcAscent( rInf, pPor ); 697 698 InsertPortion( rInf, pPor ); 699 pPor = NewPortion( rInf ); 700 } 701 702 if( !rInf.IsStop() ) 703 { 704 // The last right centered, decimal tab 705 SwTabPortion *pLastTab = rInf.GetLastTab(); 706 if( pLastTab ) 707 pLastTab->FormatEOL( rInf ); 708 else if( rInf.GetLast() && rInf.LastKernPortion() ) 709 rInf.GetLast()->FormatEOL( rInf ); 710 } 711 if( m_pCurr->GetNextPortion() && m_pCurr->GetNextPortion()->InNumberGrp() 712 && static_cast<SwNumberPortion*>(m_pCurr->GetNextPortion())->IsHide() ) 713 rInf.SetNumDone( false ); 714 715 // Delete fly in any case 716 ClearFly( rInf ); 717 718 // Reinit the tab overflow flag after the line 719 rInf.SetTabOverflow( false ); 720 } 721 722 void SwTextFormatter::CalcAdjustLine( SwLineLayout *pCurrent ) 723 { 724 if( SvxAdjust::Left != GetAdjust() && !pMulti) 725 { 726 pCurrent->SetFormatAdj(true); 727 if( IsFlyInCntBase() ) 728 { 729 CalcAdjLine( pCurrent ); 730 // For e.g. centered fly we need to switch the RefPoint 731 // That's why bAlways = true 732 UpdatePos( pCurrent, GetTopLeft(), GetStart(), true ); 733 } 734 } 735 } 736 737 void SwTextFormatter::CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor ) 738 { 739 bool bCalc = false; 740 if ( pPor->InFieldGrp() && static_cast<SwFieldPortion*>(pPor)->GetFont() ) 741 { 742 // Numbering + InterNetFields can keep an own font, then their size is 743 // independent from hard attribute values 744 SwFont* pFieldFnt = static_cast<SwFieldPortion*>(pPor)->m_pFont.get(); 745 SwFontSave aSave( rInf, pFieldFnt ); 746 pPor->Height( rInf.GetTextHeight() ); 747 pPor->SetAscent( rInf.GetAscent() ); 748 bCalc = true; 749 } 750 // i#89179 751 // tab portion representing the list tab of a list label gets the 752 // same height and ascent as the corresponding number portion 753 else if ( pPor->InTabGrp() && pPor->GetLen() == TextFrameIndex(0) && 754 rInf.GetLast() && rInf.GetLast()->InNumberGrp() && 755 static_cast<const SwNumberPortion*>(rInf.GetLast())->HasFont() ) 756 { 757 const SwLinePortion* pLast = rInf.GetLast(); 758 pPor->Height( pLast->Height() ); 759 pPor->SetAscent( pLast->GetAscent() ); 760 } 761 else 762 { 763 const SwLinePortion *pLast = rInf.GetLast(); 764 bool bChg = false; 765 766 // In empty lines the attributes are switched on via SeekStart 767 const bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx(); 768 if ( pPor->IsQuoVadisPortion() ) 769 bChg = SeekStartAndChg( rInf, true ); 770 else 771 { 772 if( bFirstPor ) 773 { 774 if( !rInf.GetText().isEmpty() ) 775 { 776 if ( pPor->GetLen() || !rInf.GetIdx() 777 || ( m_pCurr != pLast && !pLast->IsFlyPortion() ) 778 || !m_pCurr->IsRest() ) // instead of !rInf.GetRest() 779 bChg = SeekAndChg( rInf ); 780 else 781 bChg = SeekAndChgBefore( rInf ); 782 } 783 else if ( pMulti ) 784 // do not open attributes starting at 0 in empty multi 785 // portions (rotated numbering followed by a footnote 786 // can cause trouble, because the footnote attribute 787 // starts at 0, but if we open it, the attribute handler 788 // cannot handle it. 789 bChg = false; 790 else 791 bChg = SeekStartAndChg( rInf ); 792 } 793 else 794 bChg = SeekAndChg( rInf ); 795 } 796 if( bChg || bFirstPor || !pPor->GetAscent() 797 || !rInf.GetLast()->InTextGrp() ) 798 { 799 pPor->SetAscent( rInf.GetAscent() ); 800 pPor->Height( rInf.GetTextHeight() ); 801 bCalc = true; 802 } 803 else 804 { 805 pPor->Height( pLast->Height() ); 806 pPor->SetAscent( pLast->GetAscent() ); 807 } 808 } 809 810 if( pPor->InTextGrp() && bCalc ) 811 { 812 pPor->SetAscent(pPor->GetAscent() + 813 rInf.GetFont()->GetTopBorderSpace()); 814 pPor->Height(pPor->Height() + 815 rInf.GetFont()->GetTopBorderSpace() + 816 rInf.GetFont()->GetBottomBorderSpace() ); 817 } 818 } 819 820 class SwMetaPortion : public SwTextPortion 821 { 822 public: 823 SwMetaPortion() { SetWhichPor( PortionType::Meta ); } 824 virtual void Paint( const SwTextPaintInfo &rInf ) const override; 825 }; 826 827 void SwMetaPortion::Paint( const SwTextPaintInfo &rInf ) const 828 { 829 if ( Width() ) 830 { 831 rInf.DrawViewOpt( *this, PortionType::Meta ); 832 SwTextPortion::Paint( rInf ); 833 } 834 } 835 836 namespace sw { namespace mark { 837 OUString ExpandFieldmark(IFieldmark* pBM) 838 { 839 const IFieldmark::parameter_map_t* const pParameters = pBM->GetParameters(); 840 sal_Int32 nCurrentIdx = 0; 841 const IFieldmark::parameter_map_t::const_iterator pResult = pParameters->find(OUString(ODF_FORMDROPDOWN_RESULT)); 842 if(pResult != pParameters->end()) 843 pResult->second >>= nCurrentIdx; 844 845 const IFieldmark::parameter_map_t::const_iterator pListEntries = pParameters->find(OUString(ODF_FORMDROPDOWN_LISTENTRY)); 846 if (pListEntries != pParameters->end()) 847 { 848 uno::Sequence< OUString > vListEntries; 849 pListEntries->second >>= vListEntries; 850 if (nCurrentIdx < vListEntries.getLength()) 851 return vListEntries[nCurrentIdx]; 852 } 853 854 static const sal_Unicode vEnSpaces[ODF_FORMFIELD_DEFAULT_LENGTH] = {8194, 8194, 8194, 8194, 8194}; 855 return OUString(vEnSpaces, ODF_FORMFIELD_DEFAULT_LENGTH); 856 } 857 } } 858 859 SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const 860 { 861 SwTextPortion *pPor = nullptr; 862 if( GetFnt()->IsTox() ) 863 { 864 pPor = new SwToxPortion; 865 } 866 else if ( GetFnt()->IsInputField() ) 867 { 868 pPor = new SwTextInputFieldPortion(); 869 } 870 else 871 { 872 if( GetFnt()->IsRef() ) 873 pPor = new SwRefPortion; 874 else if (GetFnt()->IsMeta()) 875 { 876 pPor = new SwMetaPortion; 877 } 878 else 879 { 880 // Only at the End! 881 // If pCurr does not have a width, it can however already have content. 882 // E.g. for non-displayable characters 883 884 auto const ch(rInf.GetText()[sal_Int32(rInf.GetIdx())]); 885 SwTextFrame const*const pFrame(rInf.GetTextFrame()); 886 SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx())); 887 sw::mark::IFieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getFieldmarkFor(aPosition); 888 if(pBM != nullptr && pBM->GetFieldname( ) == ODF_FORMDATE) 889 { 890 if (ch == CH_TXT_ATR_FIELDSTART) 891 pPor = new SwFieldFormDatePortion(pBM, true); 892 else if (ch == CH_TXT_ATR_FIELDEND) 893 pPor = new SwFieldFormDatePortion(pBM, false); 894 } 895 else if (ch == CH_TXT_ATR_FIELDSTART) 896 pPor = new SwFieldMarkPortion(); 897 else if (ch == CH_TXT_ATR_FIELDEND) 898 pPor = new SwFieldMarkPortion(); 899 else if (ch == CH_TXT_ATR_FORMELEMENT) 900 { 901 OSL_ENSURE(pBM != nullptr, "Where is my form field bookmark???"); 902 if (pBM != nullptr) 903 { 904 if (pBM->GetFieldname( ) == ODF_FORMCHECKBOX) 905 { 906 pPor = new SwFieldFormCheckboxPortion(); 907 } 908 else if (pBM->GetFieldname( ) == ODF_FORMDROPDOWN) 909 { 910 pPor = new SwFieldFormDropDownPortion(pBM, sw::mark::ExpandFieldmark(pBM)); 911 } 912 /* we need to check for ODF_FORMTEXT for scenario having FormFields inside FORMTEXT. 913 * Otherwise file will crash on open. 914 */ 915 else if (pBM->GetFieldname( ) == ODF_FORMTEXT) 916 { 917 pPor = new SwFieldMarkPortion(); 918 } 919 } 920 } 921 if( !pPor ) 922 { 923 if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() ) 924 pPor = m_pCurr; 925 else 926 pPor = new SwTextPortion; 927 } 928 } 929 } 930 return pPor; 931 } 932 933 // We calculate the length, the following portion limits are defined: 934 // 1) Tabs 935 // 2) Linebreaks 936 // 3) CH_TXTATR_BREAKWORD / CH_TXTATR_INWORD 937 // 4) next attribute change 938 939 SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf ) 940 { 941 // If we're at the line's beginning, we take pCurr 942 // If pCurr is not derived from SwTextPortion, we need to duplicate 943 Seek( rInf.GetIdx() ); 944 SwTextPortion *pPor = WhichTextPor( rInf ); 945 946 // until next attribute change: 947 const TextFrameIndex nNextAttr = GetNextAttr(); 948 TextFrameIndex nNextChg = std::min(nNextAttr, TextFrameIndex(rInf.GetText().getLength())); 949 950 // end of script type: 951 const TextFrameIndex nNextScript = m_pScriptInfo->NextScriptChg(rInf.GetIdx()); 952 nNextChg = std::min( nNextChg, nNextScript ); 953 954 // end of direction: 955 const TextFrameIndex nNextDir = m_pScriptInfo->NextDirChg(rInf.GetIdx()); 956 nNextChg = std::min( nNextChg, nNextDir ); 957 958 // hidden change (potentially via bookmark): 959 const TextFrameIndex nNextHidden = m_pScriptInfo->NextHiddenChg(rInf.GetIdx()); 960 nNextChg = std::min( nNextChg, nNextHidden ); 961 962 // Turbo boost: 963 // We assume that font characters are not larger than twice 964 // as wide as height. 965 // Very crazy: we need to take the ascent into account. 966 967 // Mind the trap! GetSize() contains the wished-for height, the real height 968 // is only known in CalcAscent! 969 970 // The ratio is even crazier: a blank in Times New Roman has an ascent of 971 // 182, a height of 200 and a width of 53! 972 // It follows that a line with a lot of blanks is processed incorrectly. 973 // Therefore we increase from factor 2 to 8 (due to negative kerning). 974 975 pPor->SetLen(TextFrameIndex(1)); 976 CalcAscent( rInf, pPor ); 977 978 const SwFont* pTmpFnt = rInf.GetFont(); 979 sal_Int32 nExpect = std::min( sal_Int32( pTmpFnt->GetHeight() ), 980 sal_Int32( pPor->GetAscent() ) ) / 8; 981 if ( !nExpect ) 982 nExpect = 1; 983 nExpect = sal_Int32(rInf.GetIdx()) + (rInf.GetLineWidth() / nExpect); 984 if (TextFrameIndex(nExpect) > rInf.GetIdx() && nNextChg > TextFrameIndex(nExpect)) 985 nNextChg = TextFrameIndex(std::min(nExpect, rInf.GetText().getLength())); 986 987 // we keep an invariant during method calls: 988 // there are no portion ending characters like hard spaces 989 // or tabs in [ nLeftScanIdx, nRightScanIdx ] 990 if ( nLeftScanIdx <= rInf.GetIdx() && rInf.GetIdx() <= nRightScanIdx ) 991 { 992 if ( nNextChg > nRightScanIdx ) 993 nNextChg = nRightScanIdx = 994 rInf.ScanPortionEnd( nRightScanIdx, nNextChg ); 995 } 996 else 997 { 998 nLeftScanIdx = rInf.GetIdx(); 999 nNextChg = nRightScanIdx = 1000 rInf.ScanPortionEnd( rInf.GetIdx(), nNextChg ); 1001 } 1002 1003 pPor->SetLen( nNextChg - rInf.GetIdx() ); 1004 rInf.SetLen( pPor->GetLen() ); 1005 return pPor; 1006 } 1007 1008 SwLinePortion *SwTextFormatter::WhichFirstPortion(SwTextFormatInfo &rInf) 1009 { 1010 SwLinePortion *pPor = nullptr; 1011 1012 if( rInf.GetRest() ) 1013 { 1014 // Tabs and fields 1015 if( '\0' != rInf.GetHookChar() ) 1016 return nullptr; 1017 1018 pPor = rInf.GetRest(); 1019 if( pPor->IsErgoSumPortion() ) 1020 rInf.SetErgoDone(true); 1021 else 1022 if( pPor->IsFootnoteNumPortion() ) 1023 rInf.SetFootnoteDone(true); 1024 else 1025 if( pPor->InNumberGrp() ) 1026 rInf.SetNumDone(true); 1027 1028 rInf.SetRest(nullptr); 1029 m_pCurr->SetRest( true ); 1030 return pPor; 1031 } 1032 1033 // We can stand in the follow, it's crucial that 1034 // pFrame->GetOfst() == 0! 1035 if( rInf.GetIdx() ) 1036 { 1037 // We now too can elongate FootnotePortions and ErgoSumPortions 1038 1039 // 1. The ErgoSumTexts 1040 if( !rInf.IsErgoDone() ) 1041 { 1042 if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() ) 1043 pPor = static_cast<SwLinePortion*>(NewErgoSumPortion( rInf )); 1044 rInf.SetErgoDone( true ); 1045 } 1046 1047 // 2. Arrow portions 1048 if( !pPor && !rInf.IsArrowDone() ) 1049 { 1050 if( m_pFrame->GetOfst() && !m_pFrame->IsFollow() && 1051 rInf.GetIdx() == m_pFrame->GetOfst() ) 1052 pPor = new SwArrowPortion( *m_pCurr ); 1053 rInf.SetArrowDone( true ); 1054 } 1055 1056 // 3. Kerning portions at beginning of line in grid mode 1057 if ( ! pPor && ! m_pCurr->GetNextPortion() ) 1058 { 1059 SwTextGridItem const*const pGrid( 1060 GetGridItem(GetTextFrame()->FindPageFrame())); 1061 if ( pGrid ) 1062 pPor = new SwKernPortion( *m_pCurr ); 1063 } 1064 1065 // 4. The line rests (multiline fields) 1066 if( !pPor ) 1067 { 1068 pPor = rInf.GetRest(); 1069 // Only for pPor of course 1070 if( pPor ) 1071 { 1072 m_pCurr->SetRest( true ); 1073 rInf.SetRest(nullptr); 1074 } 1075 } 1076 } 1077 else 1078 { 1079 // 5. The foot note count 1080 if( !rInf.IsFootnoteDone() ) 1081 { 1082 OSL_ENSURE( ( ! rInf.IsMulti() && ! pMulti ) || pMulti->HasRotation(), 1083 "Rotated number portion trouble" ); 1084 1085 const bool bFootnoteNum = m_pFrame->IsFootnoteNumFrame(); 1086 rInf.GetParaPortion()->SetFootnoteNum( bFootnoteNum ); 1087 if( bFootnoteNum ) 1088 pPor = static_cast<SwLinePortion*>(NewFootnoteNumPortion( rInf )); 1089 rInf.SetFootnoteDone( true ); 1090 } 1091 1092 // 6. The ErgoSumTexts of course also exist in the TextMaster, 1093 // it's crucial whether the SwFootnoteFrame is aFollow 1094 if( !rInf.IsErgoDone() && !pPor && ! rInf.IsMulti() ) 1095 { 1096 if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() ) 1097 pPor = static_cast<SwLinePortion*>(NewErgoSumPortion( rInf )); 1098 rInf.SetErgoDone( true ); 1099 } 1100 1101 // 7. The numbering 1102 if( !rInf.IsNumDone() && !pPor ) 1103 { 1104 OSL_ENSURE( ( ! rInf.IsMulti() && ! pMulti ) || pMulti->HasRotation(), 1105 "Rotated number portion trouble" ); 1106 1107 // If we're in the follow, then of course not 1108 if (GetTextFrame()->GetTextNodeForParaProps()->GetNumRule()) 1109 pPor = static_cast<SwLinePortion*>(NewNumberPortion( rInf )); 1110 rInf.SetNumDone( true ); 1111 } 1112 // 8. The DropCaps 1113 if( !pPor && GetDropFormat() && ! rInf.IsMulti() ) 1114 pPor = static_cast<SwLinePortion*>(NewDropPortion( rInf )); 1115 1116 // 9. Kerning portions at beginning of line in grid mode 1117 if ( !pPor && !m_pCurr->GetNextPortion() ) 1118 { 1119 SwTextGridItem const*const pGrid( 1120 GetGridItem(GetTextFrame()->FindPageFrame())); 1121 if ( pGrid ) 1122 pPor = new SwKernPortion( *m_pCurr ); 1123 } 1124 } 1125 1126 // 10. Decimal tab portion at the beginning of each line in table cells 1127 if ( !pPor && !m_pCurr->GetNextPortion() && 1128 GetTextFrame()->IsInTab() && 1129 GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT)) 1130 { 1131 pPor = NewTabPortion( rInf, true ); 1132 } 1133 1134 // 11. suffix of meta-field 1135 if (!pPor) 1136 { 1137 pPor = TryNewNoLengthPortion(rInf); 1138 } 1139 1140 return pPor; 1141 } 1142 1143 static bool lcl_OldFieldRest( const SwLineLayout* pCurr ) 1144 { 1145 if( !pCurr->GetNext() ) 1146 return false; 1147 const SwLinePortion *pPor = pCurr->GetNext()->GetNextPortion(); 1148 bool bRet = false; 1149 while( pPor && !bRet ) 1150 { 1151 bRet = (pPor->InFieldGrp() && static_cast<const SwFieldPortion*>(pPor)->IsFollow()) || 1152 (pPor->IsMultiPortion() && static_cast<const SwMultiPortion*>(pPor)->IsFollowField()); 1153 if( !pPor->GetLen() ) 1154 break; 1155 pPor = pPor->GetNextPortion(); 1156 } 1157 return bRet; 1158 } 1159 1160 /* NewPortion sets rInf.nLen 1161 * A SwTextPortion is limited by a tab, break, txtatr or attr change 1162 * We can have three cases: 1163 * 1) The line is full and the wrap was not emulated 1164 * -> return 0; 1165 * 2) The line is full and a wrap was emulated 1166 * -> Reset width and return new FlyPortion 1167 * 3) We need to construct a new portion 1168 * -> CalcFlyWidth emulates the width and return portion, if needed 1169 */ 1170 1171 SwLinePortion *SwTextFormatter::NewPortion( SwTextFormatInfo &rInf ) 1172 { 1173 // Underflow takes precedence 1174 rInf.SetStopUnderflow( false ); 1175 if( rInf.GetUnderflow() ) 1176 { 1177 OSL_ENSURE( rInf.IsFull(), "SwTextFormatter::NewPortion: underflow but not full" ); 1178 return Underflow( rInf ); 1179 } 1180 1181 // If the line is full, flys and Underflow portions could be waiting ... 1182 if( rInf.IsFull() ) 1183 { 1184 // LineBreaks and Flys (bug05.sdw) 1185 // IsDummy() 1186 if( rInf.IsNewLine() && (!rInf.GetFly() || !m_pCurr->IsDummy()) ) 1187 return nullptr; 1188 1189 // When the text bumps into the Fly, or when the Fly comes first because 1190 // it juts out over the left edge, GetFly() is returned. 1191 // When IsFull() and no GetFly() is available, naturally zero is returned. 1192 if( rInf.GetFly() ) 1193 { 1194 if( rInf.GetLast()->IsBreakPortion() ) 1195 { 1196 delete rInf.GetFly(); 1197 rInf.SetFly( nullptr ); 1198 } 1199 1200 return rInf.GetFly(); 1201 } 1202 1203 // A nasty special case: A frame without wrap overlaps the Footnote area. 1204 // We must declare the Footnote portion as rest of line, so that 1205 // SwTextFrame::Format doesn't abort (the text mass already was formatted). 1206 if( rInf.GetRest() ) 1207 rInf.SetNewLine( true ); 1208 else 1209 { 1210 // When the next line begins with a rest of a field, but now no 1211 // rest remains, the line must definitely be formatted anew! 1212 if( lcl_OldFieldRest( GetCurr() ) ) 1213 rInf.SetNewLine( true ); 1214 else 1215 { 1216 SwLinePortion *pFirst = WhichFirstPortion( rInf ); 1217 if( pFirst ) 1218 { 1219 rInf.SetNewLine( true ); 1220 if( pFirst->InNumberGrp() ) 1221 rInf.SetNumDone( false) ; 1222 delete pFirst; 1223 } 1224 } 1225 } 1226 1227 return nullptr; 1228 } 1229 1230 SwLinePortion *pPor = WhichFirstPortion( rInf ); 1231 1232 // Check for Hidden Portion: 1233 if ( !pPor ) 1234 { 1235 TextFrameIndex nEnd = rInf.GetIdx(); 1236 if ( ::lcl_BuildHiddenPortion( rInf, nEnd ) ) 1237 pPor = new SwHiddenTextPortion( nEnd - rInf.GetIdx() ); 1238 } 1239 1240 if( !pPor ) 1241 { 1242 if( ( !pMulti || pMulti->IsBidi() ) && 1243 // i#42734 1244 // No multi portion if there is a hook character waiting: 1245 ( !rInf.GetRest() || '\0' == rInf.GetHookChar() ) ) 1246 { 1247 // We open a multiportion part, if we enter a multi-line part 1248 // of the paragraph. 1249 TextFrameIndex nEnd = rInf.GetIdx(); 1250 std::unique_ptr<SwMultiCreator> pCreate = rInf.GetMultiCreator( nEnd, pMulti ); 1251 if( pCreate ) 1252 { 1253 SwMultiPortion* pTmp = nullptr; 1254 1255 if ( SwMultiCreatorId::Bidi == pCreate->nId ) 1256 pTmp = new SwBidiPortion( nEnd, pCreate->nLevel ); 1257 else if ( SwMultiCreatorId::Ruby == pCreate->nId ) 1258 { 1259 pTmp = new SwRubyPortion( *pCreate, *rInf.GetFont(), 1260 GetTextFrame()->GetDoc().getIDocumentSettingAccess(), 1261 nEnd, TextFrameIndex(0), rInf ); 1262 } 1263 else if( SwMultiCreatorId::Rotate == pCreate->nId ) 1264 pTmp = new SwRotatedPortion( *pCreate, nEnd, 1265 GetTextFrame()->IsRightToLeft() ); 1266 else 1267 pTmp = new SwDoubleLinePortion( *pCreate, nEnd ); 1268 1269 pCreate.reset(); 1270 CalcFlyWidth( rInf ); 1271 1272 return pTmp; 1273 } 1274 } 1275 // Tabs and Fields 1276 sal_Unicode cChar = rInf.GetHookChar(); 1277 1278 if( cChar ) 1279 { 1280 /* We fetch cChar again to be sure that the tab is pending now and 1281 * didn't move to the next line (as happens behind frames). 1282 * However, when a FieldPortion is in the rest, we must naturally fetch 1283 * the cChar from the field content, e.g. DecimalTabs and fields (22615) 1284 */ 1285 if( !rInf.GetRest() || !rInf.GetRest()->InFieldGrp() ) 1286 cChar = rInf.GetChar( rInf.GetIdx() ); 1287 rInf.ClearHookChar(); 1288 } 1289 else 1290 { 1291 if (rInf.GetIdx() >= TextFrameIndex(rInf.GetText().getLength())) 1292 { 1293 rInf.SetFull(true); 1294 CalcFlyWidth( rInf ); 1295 return pPor; 1296 } 1297 cChar = rInf.GetChar( rInf.GetIdx() ); 1298 } 1299 1300 switch( cChar ) 1301 { 1302 case CH_TAB: 1303 pPor = NewTabPortion( rInf, false ); break; 1304 1305 case CH_BREAK: 1306 pPor = new SwBreakPortion( *rInf.GetLast() ); break; 1307 1308 case CHAR_SOFTHYPHEN: // soft hyphen 1309 pPor = new SwSoftHyphPortion; break; 1310 1311 case CHAR_HARDBLANK: // no-break space 1312 // Please check tdf#115067 if you want to edit the char 1313 pPor = new SwBlankPortion( cChar ); break; 1314 1315 case CHAR_HARDHYPHEN: // non-breaking hyphen 1316 pPor = new SwBlankPortion( '-' ); break; 1317 1318 case CHAR_ZWSP: // zero width space 1319 case CHAR_ZWNBSP : // word joiner 1320 pPor = new SwControlCharPortion( cChar ); break; 1321 1322 case CH_TXTATR_BREAKWORD: 1323 case CH_TXTATR_INWORD: 1324 if( rInf.HasHint( rInf.GetIdx() ) ) 1325 { 1326 pPor = NewExtraPortion( rInf ); 1327 break; 1328 } 1329 [[fallthrough]]; 1330 default : 1331 { 1332 SwTabPortion* pLastTabPortion = rInf.GetLastTab(); 1333 if ( pLastTabPortion && cChar == rInf.GetTabDecimal() ) 1334 { 1335 // Abandon dec. tab position if line is full 1336 // We have a decimal tab portion in the line and the next character has to be 1337 // aligned at the tab stop position. We store the width from the beginning of 1338 // the tab stop portion up to the portion containing the decimal separator: 1339 if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) /*rInf.GetVsh()->IsTabCompat();*/ && 1340 PortionType::TabDecimal == pLastTabPortion->GetWhichPor() ) 1341 { 1342 OSL_ENSURE( rInf.X() >= pLastTabPortion->GetFix(), "Decimal tab stop position cannot be calculated" ); 1343 const sal_uInt16 nWidthOfPortionsUpToDecimalPosition = static_cast<sal_uInt16>(rInf.X() - pLastTabPortion->GetFix() ); 1344 static_cast<SwTabDecimalPortion*>(pLastTabPortion)->SetWidthOfPortionsUpToDecimalPosition( nWidthOfPortionsUpToDecimalPosition ); 1345 rInf.SetTabDecimal( 0 ); 1346 } 1347 else 1348 rInf.SetFull( rInf.GetLastTab()->Format( rInf ) ); 1349 } 1350 1351 if( rInf.GetRest() ) 1352 { 1353 if( rInf.IsFull() ) 1354 { 1355 rInf.SetNewLine(true); 1356 return nullptr; 1357 } 1358 pPor = rInf.GetRest(); 1359 rInf.SetRest(nullptr); 1360 } 1361 else 1362 { 1363 if( rInf.IsFull() ) 1364 return nullptr; 1365 pPor = NewTextPortion( rInf ); 1366 } 1367 break; 1368 } 1369 } 1370 1371 // if a portion is created despite there being a pending RestPortion, 1372 // then it is a field which has been split (e.g. because it contains a Tab) 1373 if( pPor && rInf.GetRest() ) 1374 pPor->SetLen(TextFrameIndex(0)); 1375 1376 // robust: 1377 if( !pPor || rInf.IsStop() ) 1378 { 1379 delete pPor; 1380 return nullptr; 1381 } 1382 } 1383 1384 assert(pPor && "can only reach here with pPor existing"); 1385 1386 // Special portions containing numbers (footnote anchor, footnote number, 1387 // numbering) can be contained in a rotated portion, if the user 1388 // choose a rotated character attribute. 1389 if (!pMulti) 1390 { 1391 if ( pPor->IsFootnotePortion() ) 1392 { 1393 const SwTextFootnote* pTextFootnote = static_cast<SwFootnotePortion*>(pPor)->GetTextFootnote(); 1394 1395 if ( pTextFootnote ) 1396 { 1397 SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pTextFootnote->GetFootnote()); 1398 const SwDoc *const pDoc = &rInf.GetTextFrame()->GetDoc(); 1399 const SwEndNoteInfo* pInfo; 1400 if( rFootnote.IsEndNote() ) 1401 pInfo = &pDoc->GetEndNoteInfo(); 1402 else 1403 pInfo = &pDoc->GetFootnoteInfo(); 1404 const SwAttrSet& rSet = pInfo->GetAnchorCharFormat(const_cast<SwDoc&>(*pDoc))->GetAttrSet(); 1405 1406 const SfxPoolItem* pItem; 1407 sal_uInt16 nDir = 0; 1408 if( SfxItemState::SET == rSet.GetItemState( RES_CHRATR_ROTATE, 1409 true, &pItem )) 1410 nDir = static_cast<const SvxCharRotateItem*>(pItem)->GetValue(); 1411 1412 if ( 0 != nDir ) 1413 { 1414 delete pPor; 1415 pPor = new SwRotatedPortion(rInf.GetIdx() + TextFrameIndex(1), 1416 900 == nDir 1417 ? DIR_BOTTOM2TOP 1418 : DIR_TOP2BOTTOM ); 1419 } 1420 } 1421 } 1422 else if ( pPor->InNumberGrp() ) 1423 { 1424 const SwFont* pNumFnt = static_cast<SwFieldPortion*>(pPor)->GetFont(); 1425 1426 if ( pNumFnt ) 1427 { 1428 sal_uInt16 nDir = pNumFnt->GetOrientation( rInf.GetTextFrame()->IsVertical() ); 1429 if ( 0 != nDir ) 1430 { 1431 delete pPor; 1432 pPor = new SwRotatedPortion(TextFrameIndex(0), 900 == nDir 1433 ? DIR_BOTTOM2TOP 1434 : DIR_TOP2BOTTOM ); 1435 1436 rInf.SetNumDone( false ); 1437 rInf.SetFootnoteDone( false ); 1438 } 1439 } 1440 } 1441 } 1442 1443 // The font is set in output device, 1444 // the ascent and the height will be calculated. 1445 if( !pPor->GetAscent() && !pPor->Height() ) 1446 CalcAscent( rInf, pPor ); 1447 rInf.SetLen( pPor->GetLen() ); 1448 1449 // In CalcFlyWidth Width() will be shortened if a FlyPortion is present. 1450 CalcFlyWidth( rInf ); 1451 1452 // One must not forget that pCurr as GetLast() must provide reasonable values: 1453 if( !m_pCurr->Height() ) 1454 { 1455 OSL_ENSURE( m_pCurr->Height(), "SwTextFormatter::NewPortion: limbo dance" ); 1456 m_pCurr->Height( pPor->Height() ); 1457 m_pCurr->SetAscent( pPor->GetAscent() ); 1458 } 1459 1460 OSL_ENSURE(pPor->Height(), "SwTextFormatter::NewPortion: something went wrong"); 1461 if( pPor->IsPostItsPortion() && rInf.X() >= rInf.Width() && rInf.GetFly() ) 1462 { 1463 delete pPor; 1464 pPor = rInf.GetFly(); 1465 } 1466 return pPor; 1467 } 1468 1469 TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) 1470 { 1471 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), 1472 "SwTextFormatter::FormatLine( nStartPos ) with unswapped frame" ); 1473 1474 // For the formatting routines, we set pOut to the reference device. 1475 SwHookOut aHook( GetInfo() ); 1476 if (GetInfo().GetLen() < TextFrameIndex(GetInfo().GetText().getLength())) 1477 GetInfo().SetLen(TextFrameIndex(GetInfo().GetText().getLength())); 1478 1479 bool bBuild = true; 1480 SetFlyInCntBase( false ); 1481 GetInfo().SetLineHeight( 0 ); 1482 GetInfo().SetLineNetHeight( 0 ); 1483 1484 // Recycling must be suppressed by changed line height and also 1485 // by changed ascent (lowering of baseline). 1486 const sal_uInt16 nOldHeight = m_pCurr->Height(); 1487 const sal_uInt16 nOldAscent = m_pCurr->GetAscent(); 1488 1489 m_pCurr->SetEndHyph( false ); 1490 m_pCurr->SetMidHyph( false ); 1491 1492 // fly positioning can make it necessary format a line several times 1493 // for this, we have to keep a copy of our rest portion 1494 SwLinePortion* pField = GetInfo().GetRest(); 1495 std::unique_ptr<SwFieldPortion> xSaveField; 1496 1497 if ( pField && pField->InFieldGrp() && !pField->IsFootnotePortion() ) 1498 xSaveField.reset(new SwFieldPortion( *static_cast<SwFieldPortion*>(pField) )); 1499 1500 // for an optimal repaint rectangle, we want to compare fly portions 1501 // before and after the BuildPortions call 1502 const bool bOptimizeRepaint = AllowRepaintOpt(); 1503 TextFrameIndex const nOldLineEnd = nStartPos + m_pCurr->GetLen(); 1504 std::vector<long> flyStarts; 1505 1506 // these are the conditions for a fly position comparison 1507 if ( bOptimizeRepaint && m_pCurr->IsFly() ) 1508 { 1509 SwLinePortion* pPor = m_pCurr->GetFirstPortion(); 1510 long nPOfst = 0; 1511 while ( pPor ) 1512 { 1513 if ( pPor->IsFlyPortion() ) 1514 // insert start value of fly portion 1515 flyStarts.push_back( nPOfst ); 1516 1517 nPOfst += pPor->Width(); 1518 pPor = pPor->GetNextPortion(); 1519 } 1520 } 1521 1522 // Here soon the underflow check follows. 1523 while( bBuild ) 1524 { 1525 GetInfo().SetFootnoteInside( false ); 1526 GetInfo().SetOtherThanFootnoteInside( false ); 1527 1528 // These values must not be reset by FormatReset(); 1529 const bool bOldNumDone = GetInfo().IsNumDone(); 1530 const bool bOldArrowDone = GetInfo().IsArrowDone(); 1531 const bool bOldErgoDone = GetInfo().IsErgoDone(); 1532 1533 // besides other things, this sets the repaint offset to 0 1534 FormatReset( GetInfo() ); 1535 1536 GetInfo().SetNumDone( bOldNumDone ); 1537 GetInfo().SetArrowDone( bOldArrowDone ); 1538 GetInfo().SetErgoDone( bOldErgoDone ); 1539 1540 // build new portions for this line 1541 BuildPortions( GetInfo() ); 1542 1543 if( GetInfo().IsStop() ) 1544 { 1545 m_pCurr->SetLen(TextFrameIndex(0)); 1546 m_pCurr->Height( GetFrameRstHeight() + 1 ); 1547 m_pCurr->SetRealHeight( GetFrameRstHeight() + 1 ); 1548 m_pCurr->Width(0); 1549 m_pCurr->Truncate(); 1550 return nStartPos; 1551 } 1552 else if( GetInfo().IsDropInit() ) 1553 { 1554 DropInit(); 1555 GetInfo().SetDropInit( false ); 1556 } 1557 1558 m_pCurr->CalcLine( *this, GetInfo() ); 1559 CalcRealHeight( GetInfo().IsNewLine() ); 1560 1561 //i#120864 For Special case that at the first calculation couldn't get 1562 //correct height. And need to recalculate for the right height. 1563 SwLinePortion* pPorTmp = m_pCurr->GetNextPortion(); 1564 if ( IsFlyInCntBase() && (!IsQuick() || (pPorTmp && pPorTmp->IsFlyCntPortion() && !pPorTmp->GetNextPortion() && 1565 m_pCurr->Height() > pPorTmp->Height()))) 1566 { 1567 sal_uInt16 nTmpAscent, nTmpHeight; 1568 CalcAscentAndHeight( nTmpAscent, nTmpHeight ); 1569 AlignFlyInCntBase( Y() + long( nTmpAscent ) ); 1570 m_pCurr->CalcLine( *this, GetInfo() ); 1571 CalcRealHeight(); 1572 } 1573 1574 // bBuild decides if another lap of honor is done 1575 if ( m_pCurr->GetRealHeight() <= GetInfo().GetLineHeight() ) 1576 { 1577 m_pCurr->SetRealHeight( GetInfo().GetLineHeight() ); 1578 bBuild = false; 1579 } 1580 else 1581 { 1582 bBuild = ( GetInfo().GetTextFly().IsOn() && ChkFlyUnderflow(GetInfo()) ) 1583 || GetInfo().CheckFootnotePortion(m_pCurr); 1584 if( bBuild ) 1585 { 1586 GetInfo().SetNumDone( bOldNumDone ); 1587 GetInfo().ResetMaxWidthDiff(); 1588 1589 // delete old rest 1590 if ( GetInfo().GetRest() ) 1591 { 1592 delete GetInfo().GetRest(); 1593 GetInfo().SetRest( nullptr ); 1594 } 1595 1596 // set original rest portion 1597 if ( xSaveField ) 1598 GetInfo().SetRest( new SwFieldPortion( *xSaveField ) ); 1599 1600 m_pCurr->SetLen(TextFrameIndex(0)); 1601 m_pCurr->Width(0); 1602 m_pCurr->Truncate(); 1603 } 1604 } 1605 } 1606 1607 // In case of compat mode, it's possible that a tab portion is wider after 1608 // formatting than before. If this is the case, we also have to make sure 1609 // the SwLineLayout is wider as well. 1610 if (GetInfo().GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN)) 1611 { 1612 sal_uInt16 nSum = 0; 1613 SwLinePortion* pPor = m_pCurr->GetFirstPortion(); 1614 1615 while (pPor) 1616 { 1617 nSum += pPor->Width(); 1618 pPor = pPor->GetNextPortion(); 1619 } 1620 1621 if (nSum > m_pCurr->Width()) 1622 m_pCurr->Width(nSum); 1623 } 1624 1625 // calculate optimal repaint rectangle 1626 if ( bOptimizeRepaint ) 1627 { 1628 GetInfo().SetPaintOfst( ::lcl_CalcOptRepaint( *this, *m_pCurr, nOldLineEnd, flyStarts ) ); 1629 flyStarts.clear(); 1630 } 1631 else 1632 // Special case: we do not allow an optimization of the repaint 1633 // area, but during formatting the repaint offset is set to indicate 1634 // a maximum value for the offset. This value has to be reset: 1635 GetInfo().SetPaintOfst( 0 ); 1636 1637 // This corrects the start of the reformat range if something has 1638 // moved to the next line. Otherwise IsFirstReformat in AllowRepaintOpt 1639 // will give us a wrong result if we have to reformat another line 1640 GetInfo().GetParaPortion()->GetReformat().LeftMove( GetInfo().GetIdx() ); 1641 1642 // delete master copy of rest portion 1643 xSaveField.reset(); 1644 1645 TextFrameIndex const nNewStart = nStartPos + m_pCurr->GetLen(); 1646 1647 // adjust text if kana compression is enabled 1648 if ( GetInfo().CompressLine() ) 1649 { 1650 SwTwips nRepaintOfst = CalcKanaAdj( m_pCurr ); 1651 1652 // adjust repaint offset 1653 if ( nRepaintOfst < GetInfo().GetPaintOfst() ) 1654 GetInfo().SetPaintOfst( nRepaintOfst ); 1655 } 1656 1657 CalcAdjustLine( m_pCurr ); 1658 1659 if( nOldHeight != m_pCurr->Height() || nOldAscent != m_pCurr->GetAscent() ) 1660 { 1661 SetFlyInCntBase(); 1662 GetInfo().SetPaintOfst( 0 ); // changed line height => no recycling 1663 // all following line must be painted and when Flys are around, 1664 // also formatted 1665 GetInfo().SetShift( true ); 1666 } 1667 1668 if ( IsFlyInCntBase() && !IsQuick() ) 1669 UpdatePos( m_pCurr, GetTopLeft(), GetStart() ); 1670 1671 return nNewStart; 1672 } 1673 1674 void SwTextFormatter::RecalcRealHeight() 1675 { 1676 do 1677 { 1678 CalcRealHeight(); 1679 } while (Next()); 1680 } 1681 1682 void SwTextFormatter::CalcRealHeight( bool bNewLine ) 1683 { 1684 sal_uInt16 nLineHeight = m_pCurr->Height(); 1685 m_pCurr->SetClipping( false ); 1686 1687 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); 1688 if ( pGrid && GetInfo().SnapToGrid() ) 1689 { 1690 const sal_uInt16 nGridWidth = pGrid->GetBaseHeight(); 1691 const sal_uInt16 nRubyHeight = pGrid->GetRubyHeight(); 1692 const bool bRubyTop = ! pGrid->GetRubyTextBelow(); 1693 1694 nLineHeight = nGridWidth + nRubyHeight; 1695 const sal_uInt16 nAmpRatio = (m_pCurr->Height() + nLineHeight - 1)/nLineHeight; 1696 nLineHeight *= nAmpRatio; 1697 1698 const sal_uInt16 nAsc = m_pCurr->GetAscent() + 1699 ( bRubyTop ? 1700 ( nLineHeight - m_pCurr->Height() + nRubyHeight ) / 2 : 1701 ( nLineHeight - m_pCurr->Height() - nRubyHeight ) / 2 ); 1702 1703 m_pCurr->Height( nLineHeight ); 1704 m_pCurr->SetAscent( nAsc ); 1705 m_pInf->GetParaPortion()->SetFixLineHeight(); 1706 1707 // we ignore any line spacing options except from ... 1708 const SvxLineSpacingItem* pSpace = m_aLineInf.GetLineSpacing(); 1709 if ( ! IsParaLine() && pSpace && 1710 SvxInterLineSpaceRule::Prop == pSpace->GetInterLineSpaceRule() ) 1711 { 1712 sal_uLong nTmp = pSpace->GetPropLineSpace(); 1713 1714 if( nTmp < 100 ) 1715 nTmp = 100; 1716 1717 nTmp *= nLineHeight; 1718 nLineHeight = static_cast<sal_uInt16>(nTmp / 100); 1719 } 1720 1721 m_pCurr->SetRealHeight( nLineHeight ); 1722 return; 1723 } 1724 1725 // The dummy flag is set on lines that only contain flyportions, these shouldn't 1726 // consider register-true and so on. Unfortunately an empty line can be at 1727 // the end of a paragraph (empty paragraphs or behind a Shift-Return), 1728 // which should consider the register. 1729 if (!m_pCurr->IsDummy() || (!m_pCurr->GetNext() 1730 && GetStart() >= TextFrameIndex(GetTextFrame()->GetText().getLength()) 1731 && !bNewLine)) 1732 { 1733 const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing(); 1734 if( pSpace ) 1735 { 1736 switch( pSpace->GetLineSpaceRule() ) 1737 { 1738 case SvxLineSpaceRule::Auto: 1739 // shrink first line of paragraph too on spacing < 100% 1740 if (IsParaLine() && 1741 pSpace->GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop 1742 && GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE)) 1743 { 1744 long nTmp = pSpace->GetPropLineSpace(); 1745 // Word will render < 50% too but it's just not readable 1746 if( nTmp < 50 ) 1747 nTmp = nTmp ? 50 : 100; 1748 if (nTmp<100) { // code adapted from fixed line height 1749 nTmp *= nLineHeight; 1750 nTmp /= 100; 1751 if( !nTmp ) 1752 ++nTmp; 1753 nLineHeight = static_cast<sal_uInt16>(nTmp); 1754 sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5; // 80% 1755 #if 0 1756 // could do clipping here (like Word does) 1757 // but at 0.5 its unreadable either way... 1758 if( nAsc < pCurr->GetAscent() || 1759 nLineHeight - nAsc < pCurr->Height() - 1760 pCurr->GetAscent() ) 1761 pCurr->SetClipping( true ); 1762 #endif 1763 m_pCurr->SetAscent( nAsc ); 1764 m_pCurr->Height( nLineHeight ); 1765 m_pInf->GetParaPortion()->SetFixLineHeight(); 1766 } 1767 } 1768 break; 1769 case SvxLineSpaceRule::Min: 1770 { 1771 if( nLineHeight < pSpace->GetLineHeight() ) 1772 nLineHeight = pSpace->GetLineHeight(); 1773 break; 1774 } 1775 case SvxLineSpaceRule::Fix: 1776 { 1777 nLineHeight = pSpace->GetLineHeight(); 1778 const sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5; // 80% 1779 if( nAsc < m_pCurr->GetAscent() || 1780 nLineHeight - nAsc < m_pCurr->Height() - m_pCurr->GetAscent() ) 1781 m_pCurr->SetClipping( true ); 1782 m_pCurr->Height( nLineHeight ); 1783 m_pCurr->SetAscent( nAsc ); 1784 m_pInf->GetParaPortion()->SetFixLineHeight(); 1785 } 1786 break; 1787 default: OSL_FAIL( ": unknown LineSpaceRule" ); 1788 } 1789 // Note: for the _first_ line the line spacing of the previous 1790 // paragraph is applied in SwFlowFrame::CalcUpperSpace() 1791 if( !IsParaLine() ) 1792 switch( pSpace->GetInterLineSpaceRule() ) 1793 { 1794 case SvxInterLineSpaceRule::Off: 1795 break; 1796 case SvxInterLineSpaceRule::Prop: 1797 { 1798 long nTmp = pSpace->GetPropLineSpace(); 1799 // 50% is the minimum, if 0% we switch to the 1800 // default value 100% ... 1801 if( nTmp < 50 ) 1802 nTmp = nTmp ? 50 : 100; 1803 1804 nTmp *= nLineHeight; 1805 nTmp /= 100; 1806 if( !nTmp ) 1807 ++nTmp; 1808 nLineHeight = static_cast<sal_uInt16>(nTmp); 1809 break; 1810 } 1811 case SvxInterLineSpaceRule::Fix: 1812 { 1813 nLineHeight = nLineHeight + pSpace->GetInterLineSpace(); 1814 break; 1815 } 1816 default: OSL_FAIL( ": unknown InterLineSpaceRule" ); 1817 } 1818 } 1819 1820 if( IsRegisterOn() ) 1821 { 1822 SwTwips nTmpY = Y() + m_pCurr->GetAscent() + nLineHeight - m_pCurr->Height(); 1823 SwRectFnSet aRectFnSet(m_pFrame); 1824 if ( aRectFnSet.IsVert() ) 1825 nTmpY = m_pFrame->SwitchHorizontalToVertical( nTmpY ); 1826 nTmpY = aRectFnSet.YDiff( nTmpY, RegStart() ); 1827 const sal_uInt16 nDiff = sal_uInt16( nTmpY % RegDiff() ); 1828 if( nDiff ) 1829 nLineHeight += RegDiff() - nDiff; 1830 } 1831 } 1832 m_pCurr->SetRealHeight( nLineHeight ); 1833 } 1834 1835 void SwTextFormatter::FeedInf( SwTextFormatInfo &rInf ) const 1836 { 1837 // delete Fly in any case! 1838 ClearFly( rInf ); 1839 rInf.Init(); 1840 1841 rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() ); 1842 rInf.SetRoot( m_pCurr ); 1843 rInf.SetLineStart( m_nStart ); 1844 rInf.SetIdx( m_nStart ); 1845 rInf.Left( Left() ); 1846 rInf.Right( Right() ); 1847 rInf.First( FirstLeft() ); 1848 rInf.LeftMargin(GetLeftMargin()); 1849 1850 rInf.RealWidth( sal_uInt16(rInf.Right() - GetLeftMargin()) ); 1851 rInf.Width( rInf.RealWidth() ); 1852 if( const_cast<SwTextFormatter*>(this)->GetRedln() ) 1853 { 1854 const_cast<SwTextFormatter*>(this)->GetRedln()->Clear( const_cast<SwTextFormatter*>(this)->GetFnt() ); 1855 const_cast<SwTextFormatter*>(this)->GetRedln()->Reset(); 1856 } 1857 } 1858 1859 void SwTextFormatter::FormatReset( SwTextFormatInfo &rInf ) 1860 { 1861 m_pFirstOfBorderMerge = nullptr; 1862 m_pCurr->Truncate(); 1863 m_pCurr->Init(); 1864 if( pBlink && m_pCurr->IsBlinking() ) 1865 pBlink->Delete( m_pCurr ); 1866 1867 // delete pSpaceAdd and pKanaComp 1868 m_pCurr->FinishSpaceAdd(); 1869 m_pCurr->FinishKanaComp(); 1870 m_pCurr->ResetFlags(); 1871 FeedInf( rInf ); 1872 } 1873 1874 bool SwTextFormatter::CalcOnceMore() 1875 { 1876 if( pDropFormat ) 1877 { 1878 const sal_uInt16 nOldDrop = GetDropHeight(); 1879 CalcDropHeight( pDropFormat->GetLines() ); 1880 bOnceMore = nOldDrop != GetDropHeight(); 1881 } 1882 else 1883 bOnceMore = false; 1884 return bOnceMore; 1885 } 1886 1887 SwTwips SwTextFormatter::CalcBottomLine() const 1888 { 1889 SwTwips nRet = Y() + GetLineHeight(); 1890 SwTwips nMin = GetInfo().GetTextFly().GetMinBottom(); 1891 if( nMin && ++nMin > nRet ) 1892 { 1893 SwTwips nDist = m_pFrame->getFrameArea().Height() - m_pFrame->getFramePrintArea().Height() 1894 - m_pFrame->getFramePrintArea().Top(); 1895 if( nRet + nDist < nMin ) 1896 { 1897 const bool bRepaint = HasTruncLines() && 1898 GetInfo().GetParaPortion()->GetRepaint().Bottom() == nRet-1; 1899 nRet = nMin - nDist; 1900 if( bRepaint ) 1901 { 1902 const_cast<SwRepaint&>(GetInfo().GetParaPortion() 1903 ->GetRepaint()).Bottom( nRet-1 ); 1904 const_cast<SwTextFormatInfo&>(GetInfo()).SetPaintOfst( 0 ); 1905 } 1906 } 1907 } 1908 return nRet; 1909 } 1910 1911 // FME/OD: This routine does a limited text formatting. 1912 SwTwips SwTextFormatter::CalcFitToContent_() 1913 { 1914 FormatReset( GetInfo() ); 1915 BuildPortions( GetInfo() ); 1916 m_pCurr->CalcLine( *this, GetInfo() ); 1917 return m_pCurr->Width(); 1918 } 1919 1920 // determines if the calculation of a repaint offset is allowed 1921 // otherwise each line is painted from 0 (this is a copy of the beginning 1922 // of the former SwTextFormatter::Recycle() function 1923 bool SwTextFormatter::AllowRepaintOpt() const 1924 { 1925 // reformat position in front of current line? Only in this case 1926 // we want to set the repaint offset 1927 bool bOptimizeRepaint = m_nStart < GetInfo().GetReformatStart() && 1928 m_pCurr->GetLen(); 1929 1930 // a special case is the last line of a block adjusted paragraph: 1931 if ( bOptimizeRepaint ) 1932 { 1933 switch( GetAdjust() ) 1934 { 1935 case SvxAdjust::Block: 1936 { 1937 if( IsLastBlock() || IsLastCenter() ) 1938 bOptimizeRepaint = false; 1939 else 1940 { 1941 // ????: blank in the last master line (blocksat.sdw) 1942 bOptimizeRepaint = nullptr == m_pCurr->GetNext() && !m_pFrame->GetFollow(); 1943 if ( bOptimizeRepaint ) 1944 { 1945 SwLinePortion *pPos = m_pCurr->GetFirstPortion(); 1946 while ( pPos && !pPos->IsFlyPortion() ) 1947 pPos = pPos->GetNextPortion(); 1948 bOptimizeRepaint = !pPos; 1949 } 1950 } 1951 break; 1952 } 1953 case SvxAdjust::Center: 1954 case SvxAdjust::Right: 1955 bOptimizeRepaint = false; 1956 break; 1957 default: ; 1958 } 1959 } 1960 1961 // Again another special case: invisible SoftHyphs 1962 const TextFrameIndex nReformat = GetInfo().GetReformatStart(); 1963 if (bOptimizeRepaint && TextFrameIndex(COMPLETE_STRING) != nReformat) 1964 { 1965 const sal_Unicode cCh = nReformat >= TextFrameIndex(GetInfo().GetText().getLength()) 1966 ? 0 1967 : GetInfo().GetText()[ sal_Int32(nReformat) ]; 1968 bOptimizeRepaint = ( CH_TXTATR_BREAKWORD != cCh && CH_TXTATR_INWORD != cCh ) 1969 || ! GetInfo().HasHint( nReformat ); 1970 } 1971 1972 return bOptimizeRepaint; 1973 } 1974 1975 void SwTextFormatter::CalcUnclipped( SwTwips& rTop, SwTwips& rBottom ) 1976 { 1977 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), 1978 "SwTextFormatter::CalcUnclipped with unswapped frame" ); 1979 1980 long nFlyAsc, nFlyDesc; 1981 m_pCurr->MaxAscentDescent( rTop, rBottom, nFlyAsc, nFlyDesc ); 1982 rTop = Y() + GetCurr()->GetAscent(); 1983 rBottom = rTop + nFlyDesc; 1984 rTop -= nFlyAsc; 1985 } 1986 1987 void SwTextFormatter::UpdatePos( SwLineLayout *pCurrent, Point aStart, 1988 TextFrameIndex const nStartIdx, bool bAlways) const 1989 { 1990 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), 1991 "SwTextFormatter::UpdatePos with unswapped frame" ); 1992 1993 if( GetInfo().IsTest() ) 1994 return; 1995 SwLinePortion *pFirst = pCurrent->GetFirstPortion(); 1996 SwLinePortion *pPos = pFirst; 1997 SwTextPaintInfo aTmpInf( GetInfo() ); 1998 aTmpInf.SetpSpaceAdd( pCurrent->GetpLLSpaceAdd() ); 1999 aTmpInf.ResetSpaceIdx(); 2000 aTmpInf.SetKanaComp( pCurrent->GetpKanaComp() ); 2001 aTmpInf.ResetKanaIdx(); 2002 2003 // The frame's size 2004 aTmpInf.SetIdx( nStartIdx ); 2005 aTmpInf.SetPos( aStart ); 2006 2007 long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc; 2008 pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc ); 2009 2010 const sal_uInt16 nTmpHeight = pCurrent->GetRealHeight(); 2011 sal_uInt16 nAscent = pCurrent->GetAscent() + nTmpHeight - pCurrent->Height(); 2012 AsCharFlags nFlags = AsCharFlags::UlSpace; 2013 if( GetMulti() ) 2014 { 2015 aTmpInf.SetDirection( GetMulti()->GetDirection() ); 2016 if( GetMulti()->HasRotation() ) 2017 { 2018 nFlags |= AsCharFlags::Rotate; 2019 if( GetMulti()->IsRevers() ) 2020 { 2021 nFlags |= AsCharFlags::Reverse; 2022 aTmpInf.X( aTmpInf.X() - nAscent ); 2023 } 2024 else 2025 aTmpInf.X( aTmpInf.X() + nAscent ); 2026 } 2027 else 2028 { 2029 if ( GetMulti()->IsBidi() ) 2030 nFlags |= AsCharFlags::Bidi; 2031 aTmpInf.Y( aTmpInf.Y() + nAscent ); 2032 } 2033 } 2034 else 2035 aTmpInf.Y( aTmpInf.Y() + nAscent ); 2036 2037 while( pPos ) 2038 { 2039 // We only know one case where changing the position (caused by the 2040 // adjustment) could be relevant for a portion: We need to SetRefPoint 2041 // for FlyCntPortions. 2042 if( ( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() ) 2043 && ( bAlways || !IsQuick() ) ) 2044 { 2045 pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos ); 2046 2047 if( pPos->IsGrfNumPortion() ) 2048 { 2049 if( !nFlyAsc && !nFlyDesc ) 2050 { 2051 nTmpAscent = nAscent; 2052 nFlyAsc = nAscent; 2053 nTmpDescent = nTmpHeight - nAscent; 2054 nFlyDesc = nTmpDescent; 2055 } 2056 static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent, 2057 nFlyAsc, nFlyDesc ); 2058 } 2059 else 2060 { 2061 Point aBase( aTmpInf.GetPos() ); 2062 if ( GetInfo().GetTextFrame()->IsVertical() ) 2063 GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aBase ); 2064 2065 static_cast<SwFlyCntPortion*>(pPos)->SetBase( *aTmpInf.GetTextFrame(), 2066 aBase, nTmpAscent, nTmpDescent, nFlyAsc, 2067 nFlyDesc, nFlags ); 2068 } 2069 } 2070 if( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasFlyInContent() ) 2071 { 2072 OSL_ENSURE( !GetMulti(), "Too much multi" ); 2073 const_cast<SwTextFormatter*>(this)->pMulti = static_cast<SwMultiPortion*>(pPos); 2074 SwLineLayout *pLay = &GetMulti()->GetRoot(); 2075 Point aSt( aTmpInf.X(), aStart.Y() ); 2076 2077 if ( GetMulti()->HasBrackets() ) 2078 { 2079 OSL_ENSURE( GetMulti()->IsDouble(), "Brackets only for doubles"); 2080 aSt.AdjustX(static_cast<SwDoubleLinePortion*>(GetMulti())->PreWidth() ); 2081 } 2082 else if( GetMulti()->HasRotation() ) 2083 { 2084 aSt.AdjustY(pCurrent->GetAscent() - GetMulti()->GetAscent() ); 2085 if( GetMulti()->IsRevers() ) 2086 aSt.AdjustX(GetMulti()->Width() ); 2087 else 2088 aSt.AdjustY(GetMulti()->Height() ); 2089 } 2090 else if ( GetMulti()->IsBidi() ) 2091 // jump to end of the bidi portion 2092 aSt.AdjustX(pLay->Width() ); 2093 2094 TextFrameIndex nStIdx = aTmpInf.GetIdx(); 2095 do 2096 { 2097 UpdatePos( pLay, aSt, nStIdx, bAlways ); 2098 nStIdx = nStIdx + pLay->GetLen(); 2099 aSt.AdjustY(pLay->Height() ); 2100 pLay = pLay->GetNext(); 2101 } while ( pLay ); 2102 const_cast<SwTextFormatter*>(this)->pMulti = nullptr; 2103 } 2104 pPos->Move( aTmpInf ); 2105 pPos = pPos->GetNextPortion(); 2106 } 2107 } 2108 2109 void SwTextFormatter::AlignFlyInCntBase( long nBaseLine ) const 2110 { 2111 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), 2112 "SwTextFormatter::AlignFlyInCntBase with unswapped frame" ); 2113 2114 if( GetInfo().IsTest() ) 2115 return; 2116 SwLinePortion *pFirst = m_pCurr->GetFirstPortion(); 2117 SwLinePortion *pPos = pFirst; 2118 AsCharFlags nFlags = AsCharFlags::None; 2119 if( GetMulti() && GetMulti()->HasRotation() ) 2120 { 2121 nFlags |= AsCharFlags::Rotate; 2122 if( GetMulti()->IsRevers() ) 2123 nFlags |= AsCharFlags::Reverse; 2124 } 2125 2126 long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc; 2127 2128 while( pPos ) 2129 { 2130 if( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() ) 2131 { 2132 m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos ); 2133 2134 if( pPos->IsGrfNumPortion() ) 2135 static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent, 2136 nFlyAsc, nFlyDesc ); 2137 else 2138 { 2139 Point aBase; 2140 if ( GetInfo().GetTextFrame()->IsVertical() ) 2141 { 2142 nBaseLine = GetInfo().GetTextFrame()->SwitchHorizontalToVertical( nBaseLine ); 2143 aBase = Point( nBaseLine, static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().Y() ); 2144 } 2145 else 2146 aBase = Point( static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().X(), nBaseLine ); 2147 2148 static_cast<SwFlyCntPortion*>(pPos)->SetBase( *GetInfo().GetTextFrame(), aBase, nTmpAscent, nTmpDescent, 2149 nFlyAsc, nFlyDesc, nFlags ); 2150 } 2151 } 2152 pPos = pPos->GetNextPortion(); 2153 } 2154 } 2155 2156 bool SwTextFormatter::ChkFlyUnderflow( SwTextFormatInfo &rInf ) const 2157 { 2158 OSL_ENSURE( rInf.GetTextFly().IsOn(), "SwTextFormatter::ChkFlyUnderflow: why?" ); 2159 if( GetCurr() ) 2160 { 2161 // First we check, whether a fly overlaps with the line. 2162 // = GetLineHeight() 2163 const sal_uInt16 nHeight = GetCurr()->GetRealHeight(); 2164 SwRect aLine( GetLeftMargin(), Y(), rInf.RealWidth(), nHeight ); 2165 2166 SwRect aLineVert( aLine ); 2167 if ( m_pFrame->IsVertical() ) 2168 m_pFrame->SwitchHorizontalToVertical( aLineVert ); 2169 SwRect aInter( rInf.GetTextFly().GetFrame( aLineVert ) ); 2170 if ( m_pFrame->IsVertical() ) 2171 m_pFrame->SwitchVerticalToHorizontal( aInter ); 2172 2173 if( !aInter.HasArea() ) 2174 return false; 2175 2176 // We now check every portion that could have lowered for overlapping 2177 // with the fly. 2178 const SwLinePortion *pPos = GetCurr()->GetFirstPortion(); 2179 aLine.Pos().setY( Y() + GetCurr()->GetRealHeight() - GetCurr()->Height() ); 2180 aLine.Height( GetCurr()->Height() ); 2181 2182 while( pPos ) 2183 { 2184 aLine.Width( pPos->Width() ); 2185 2186 aLineVert = aLine; 2187 if ( m_pFrame->IsVertical() ) 2188 m_pFrame->SwitchHorizontalToVertical( aLineVert ); 2189 aInter = rInf.GetTextFly().GetFrame( aLineVert ); 2190 if ( m_pFrame->IsVertical() ) 2191 m_pFrame->SwitchVerticalToHorizontal( aInter ); 2192 2193 // New flys from below? 2194 if( !pPos->IsFlyPortion() ) 2195 { 2196 if( aInter.IsOver( aLine ) ) 2197 { 2198 aInter.Intersection_( aLine ); 2199 if( aInter.HasArea() ) 2200 { 2201 // To be evaluated during reformat of this line: 2202 // RealHeight including spacing 2203 rInf.SetLineHeight( nHeight ); 2204 // Height without extra spacing 2205 rInf.SetLineNetHeight( m_pCurr->Height() ); 2206 return true; 2207 } 2208 } 2209 } 2210 else 2211 { 2212 // The fly portion is not intersected by a fly anymore 2213 if ( ! aInter.IsOver( aLine ) ) 2214 { 2215 rInf.SetLineHeight( nHeight ); 2216 rInf.SetLineNetHeight( m_pCurr->Height() ); 2217 return true; 2218 } 2219 else 2220 { 2221 aInter.Intersection_( aLine ); 2222 2223 // No area means a fly has become invalid because of 2224 // lowering the line => reformat the line 2225 // we also have to reformat the line, if the fly size 2226 // differs from the intersection interval's size. 2227 if( ! aInter.HasArea() || 2228 static_cast<const SwFlyPortion*>(pPos)->GetFixWidth() != aInter.Width() ) 2229 { 2230 rInf.SetLineHeight( nHeight ); 2231 rInf.SetLineNetHeight( m_pCurr->Height() ); 2232 return true; 2233 } 2234 } 2235 } 2236 2237 aLine.Left( aLine.Left() + pPos->Width() ); 2238 pPos = pPos->GetNextPortion(); 2239 } 2240 } 2241 return false; 2242 } 2243 2244 void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) 2245 { 2246 if( GetMulti() || rInf.GetFly() ) 2247 return; 2248 2249 SwTextFly& rTextFly = rInf.GetTextFly(); 2250 if( !rTextFly.IsOn() || rInf.IsIgnoreFly() ) 2251 return; 2252 2253 const SwLinePortion *pLast = rInf.GetLast(); 2254 2255 long nAscent; 2256 long nTop = Y(); 2257 long nHeight; 2258 2259 if( rInf.GetLineHeight() ) 2260 { 2261 // Real line height has already been calculated, we only have to 2262 // search for intersections in the lower part of the strip 2263 nAscent = m_pCurr->GetAscent(); 2264 nHeight = rInf.GetLineNetHeight(); 2265 nTop += rInf.GetLineHeight() - nHeight; 2266 } 2267 else 2268 { 2269 // We make a first guess for the lines real height 2270 if ( ! m_pCurr->GetRealHeight() ) 2271 CalcRealHeight(); 2272 2273 nAscent = pLast->GetAscent(); 2274 nHeight = pLast->Height(); 2275 2276 if ( m_pCurr->GetRealHeight() > nHeight ) 2277 nTop += m_pCurr->GetRealHeight() - nHeight; 2278 else 2279 // Important for fixed space between lines 2280 nHeight = m_pCurr->GetRealHeight(); 2281 } 2282 2283 const long nLeftMar = GetLeftMargin(); 2284 const long nLeftMin = (rInf.X() || GetDropLeft()) ? nLeftMar : GetLeftMin(); 2285 2286 SwRect aLine( rInf.X() + nLeftMin, nTop, rInf.RealWidth() - rInf.X() 2287 + nLeftMar - nLeftMin , nHeight ); 2288 2289 // tdf#116486: consider also the upper margin from getFramePrintArea because intersections 2290 // with this additional space should lead to repositioning of paragraphs 2291 // For compatibility we grab a related compat flag: 2292 if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS)) 2293 { 2294 const long nUpper = m_pFrame->getFramePrintArea().Top(); 2295 // Increase the rectangle 2296 if( nUpper > 0 && nTop >= nUpper ) 2297 aLine.SubTop( nUpper ); 2298 } 2299 SwRect aLineVert( aLine ); 2300 if ( m_pFrame->IsRightToLeft() ) 2301 m_pFrame->SwitchLTRtoRTL( aLineVert ); 2302 2303 if ( m_pFrame->IsVertical() ) 2304 m_pFrame->SwitchHorizontalToVertical( aLineVert ); 2305 2306 // GetFrame(...) determines and returns the intersection rectangle 2307 SwRect aInter( rTextFly.GetFrame( aLineVert ) ); 2308 2309 if ( m_pFrame->IsRightToLeft() ) 2310 m_pFrame->SwitchRTLtoLTR( aInter ); 2311 2312 if ( m_pFrame->IsVertical() ) 2313 m_pFrame->SwitchVerticalToHorizontal( aInter ); 2314 2315 if( !aInter.IsOver( aLine ) ) 2316 return; 2317 2318 aLine.Left( rInf.X() + nLeftMar ); 2319 bool bForced = false; 2320 if( aInter.Left() <= nLeftMin ) 2321 { 2322 SwTwips nFrameLeft = GetTextFrame()->getFrameArea().Left(); 2323 if( GetTextFrame()->getFramePrintArea().Left() < 0 ) 2324 nFrameLeft += GetTextFrame()->getFramePrintArea().Left(); 2325 if( aInter.Left() < nFrameLeft ) 2326 aInter.Left( nFrameLeft ); 2327 2328 long nAddMar = 0; 2329 if ( m_pFrame->IsRightToLeft() ) 2330 { 2331 nAddMar = m_pFrame->getFrameArea().Right() - Right(); 2332 if ( nAddMar < 0 ) 2333 nAddMar = 0; 2334 } 2335 else 2336 nAddMar = nLeftMar - nFrameLeft; 2337 2338 aInter.Width( aInter.Width() + nAddMar ); 2339 // For a negative first line indent, we set this flag to show 2340 // that the indentation/margin has been moved. 2341 // This needs to be respected by the DefaultTab at the zero position. 2342 if( IsFirstTextLine() && HasNegFirst() ) 2343 bForced = true; 2344 } 2345 aInter.Intersection( aLine ); 2346 if( !aInter.HasArea() ) 2347 return; 2348 2349 const bool bFullLine = aLine.Left() == aInter.Left() && 2350 aLine.Right() == aInter.Right(); 2351 2352 // Although no text is left, we need to format another line, 2353 // because also empty lines need to avoid a Fly with no wrapping. 2354 if (bFullLine && rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength())) 2355 { 2356 rInf.SetNewLine( true ); 2357 // We know that for dummies, it holds ascent == height 2358 m_pCurr->SetDummy(true); 2359 } 2360 2361 // aInter becomes frame-local 2362 aInter.Pos().AdjustX( -nLeftMar ); 2363 SwFlyPortion *pFly = new SwFlyPortion( aInter ); 2364 if( bForced ) 2365 { 2366 m_pCurr->SetForcedLeftMargin(); 2367 rInf.ForcedLeftMargin( static_cast<sal_uInt16>(aInter.Width()) ); 2368 } 2369 2370 if( bFullLine ) 2371 { 2372 // In order to properly flow around Flys with different 2373 // wrapping attributes, we need to increase by units of line height. 2374 // The last avoiding line should be adjusted in height, so that 2375 // we don't get a frame spacing effect. 2376 // It is important that ascent == height, because the FlyPortion 2377 // values are transferred to pCurr in CalcLine and IsDummy() relies 2378 // on this behaviour. 2379 // To my knowledge we only have two places where DummyLines can be 2380 // created: here and in MakeFlyDummies. 2381 // IsDummy() is evaluated in IsFirstTextLine(), when moving lines 2382 // and in relation with DropCaps. 2383 pFly->Height( sal_uInt16(aInter.Height()) ); 2384 2385 // nNextTop now contains the margin's bottom edge, which we avoid 2386 // or the next margin's top edge, which we need to respect. 2387 // That means we can comfortably grow up to this value; that's how 2388 // we save a few empty lines. 2389 long nNextTop = rTextFly.GetNextTop(); 2390 if ( m_pFrame->IsVertical() ) 2391 nNextTop = m_pFrame->SwitchVerticalToHorizontal( nNextTop ); 2392 if( nNextTop > aInter.Bottom() ) 2393 { 2394 SwTwips nH = nNextTop - aInter.Top(); 2395 if( nH < SAL_MAX_UINT16 ) 2396 pFly->Height( sal_uInt16( nH ) ); 2397 } 2398 if( nAscent < pFly->Height() ) 2399 pFly->SetAscent( sal_uInt16(nAscent) ); 2400 else 2401 pFly->SetAscent( pFly->Height() ); 2402 } 2403 else 2404 { 2405 if (rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength())) 2406 { 2407 // Don't use nHeight, or we have a huge descent 2408 pFly->Height( pLast->Height() ); 2409 pFly->SetAscent( pLast->GetAscent() ); 2410 } 2411 else 2412 { 2413 pFly->Height( sal_uInt16(aInter.Height()) ); 2414 if( nAscent < pFly->Height() ) 2415 pFly->SetAscent( sal_uInt16(nAscent) ); 2416 else 2417 pFly->SetAscent( pFly->Height() ); 2418 } 2419 } 2420 2421 rInf.SetFly( pFly ); 2422 2423 if( pFly->GetFix() < rInf.Width() ) 2424 rInf.Width( pFly->GetFix() ); 2425 2426 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); 2427 if ( !pGrid ) 2428 return; 2429 2430 const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame(); 2431 const SwLayoutFrame* pBody = pPageFrame->FindBodyCont(); 2432 2433 SwRectFnSet aRectFnSet(pPageFrame); 2434 2435 const long nGridOrigin = pBody ? 2436 aRectFnSet.GetPrtLeft(*pBody) : 2437 aRectFnSet.GetPrtLeft(*pPageFrame); 2438 2439 const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc(); 2440 const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, rDoc); 2441 2442 SwTwips nStartX = GetLeftMargin(); 2443 if ( aRectFnSet.IsVert() ) 2444 { 2445 Point aPoint( nStartX, 0 ); 2446 m_pFrame->SwitchHorizontalToVertical( aPoint ); 2447 nStartX = aPoint.Y(); 2448 } 2449 2450 const SwTwips nOfst = nStartX - nGridOrigin; 2451 const SwTwips nTmpWidth = rInf.Width() + nOfst; 2452 2453 const sal_uLong i = nTmpWidth / nGridWidth + 1; 2454 2455 const long nNewWidth = ( i - 1 ) * nGridWidth - nOfst; 2456 if ( nNewWidth > 0 ) 2457 rInf.Width( static_cast<sal_uInt16>(nNewWidth) ); 2458 else 2459 rInf.Width( 0 ); 2460 2461 2462 } 2463 2464 SwFlyCntPortion *SwTextFormatter::NewFlyCntPortion( SwTextFormatInfo &rInf, 2465 SwTextAttr *pHint ) const 2466 { 2467 const SwFrame *pFrame = static_cast<SwFrame*>(m_pFrame); 2468 2469 SwFlyInContentFrame *pFly; 2470 SwFrameFormat* pFrameFormat = static_cast<SwTextFlyCnt*>(pHint)->GetFlyCnt().GetFrameFormat(); 2471 if( RES_FLYFRMFMT == pFrameFormat->Which() ) 2472 pFly = static_cast<SwTextFlyCnt*>(pHint)->GetFlyFrame(pFrame); 2473 else 2474 pFly = nullptr; 2475 // aBase is the document-global position, from which the new extra portion is placed 2476 // aBase.X() = Offset in the line after the current position 2477 // aBase.Y() = LineIter.Y() + Ascent of the current position 2478 2479 long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc; 2480 // i#11859 - use new method <SwLineLayout::MaxAscentDescent(..)> 2481 // to change line spacing behaviour at paragraph - Compatibility to MS Word 2482 //SwLinePortion *pPos = pCurr->GetFirstPortion(); 2483 //lcl_MaxAscDescent( pPos, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc ); 2484 m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc ); 2485 2486 // If the ascent of the frame is larger than the ascent of the current position, 2487 // we use this one when calculating the base, or the frame would be positioned 2488 // too much to the top, sliding down after all causing a repaint in an area 2489 // he actually never was in. 2490 sal_uInt16 nAscent = 0; 2491 2492 const bool bTextFrameVertical = GetInfo().GetTextFrame()->IsVertical(); 2493 2494 const bool bUseFlyAscent = pFly && pFly->isFrameAreaPositionValid() && 2495 0 != ( bTextFrameVertical ? 2496 pFly->GetRefPoint().X() : 2497 pFly->GetRefPoint().Y() ); 2498 2499 if ( bUseFlyAscent ) 2500 nAscent = static_cast<sal_uInt16>( std::abs( int( bTextFrameVertical ? 2501 pFly->GetRelPos().X() : 2502 pFly->GetRelPos().Y() ) ) ); 2503 2504 // Check if be prefer to use the ascent of the last portion: 2505 if ( IsQuick() || 2506 !bUseFlyAscent || 2507 nAscent < rInf.GetLast()->GetAscent() ) 2508 { 2509 nAscent = rInf.GetLast()->GetAscent(); 2510 } 2511 else if( nAscent > nFlyAsc ) 2512 nFlyAsc = nAscent; 2513 2514 Point aBase( GetLeftMargin() + rInf.X(), Y() + nAscent ); 2515 AsCharFlags nMode = IsQuick() ? AsCharFlags::Quick : AsCharFlags::None; 2516 if( GetMulti() && GetMulti()->HasRotation() ) 2517 { 2518 nMode |= AsCharFlags::Rotate; 2519 if( GetMulti()->IsRevers() ) 2520 nMode |= AsCharFlags::Reverse; 2521 } 2522 2523 Point aTmpBase( aBase ); 2524 if ( GetInfo().GetTextFrame()->IsVertical() ) 2525 GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase ); 2526 2527 SwFlyCntPortion* pRet(nullptr); 2528 if( pFly ) 2529 { 2530 pRet = sw::FlyContentPortion::Create(*GetInfo().GetTextFrame(), pFly, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode); 2531 // We need to make sure that our font is set again in the OutputDevice 2532 // It could be that the FlyInCnt was added anew and GetFlyFrame() would 2533 // in turn cause, that it'd be created anew again. 2534 // This one's frames get formatted right away, which change the font. 2535 rInf.SelectFont(); 2536 if( pRet->GetAscent() > nAscent ) 2537 { 2538 aBase.setY( Y() + pRet->GetAscent() ); 2539 nMode |= AsCharFlags::UlSpace; 2540 if( !rInf.IsTest() ) 2541 { 2542 aTmpBase = aBase; 2543 if ( GetInfo().GetTextFrame()->IsVertical() ) 2544 GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase ); 2545 2546 pRet->SetBase( *rInf.GetTextFrame(), aTmpBase, nTmpAscent, 2547 nTmpDescent, nFlyAsc, nFlyDesc, nMode ); 2548 } 2549 } 2550 } 2551 else 2552 { 2553 pRet = sw::DrawFlyCntPortion::Create(*rInf.GetTextFrame(), *pFrameFormat, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode); 2554 } 2555 return pRet; 2556 } 2557 2558 /* Drop portion is a special case, because it has parts which aren't portions 2559 but we have handle them just like portions */ 2560 void SwTextFormatter::MergeCharacterBorder( SwDropPortion const & rPortion ) 2561 { 2562 if( rPortion.GetLines() > 1 ) 2563 { 2564 SwDropPortionPart* pCurrPart = rPortion.GetPart(); 2565 while( pCurrPart ) 2566 { 2567 if( pCurrPart->GetFollow() && 2568 ::lcl_HasSameBorder(pCurrPart->GetFont(), pCurrPart->GetFollow()->GetFont()) ) 2569 { 2570 pCurrPart->SetJoinBorderWithNext(true); 2571 pCurrPart->GetFollow()->SetJoinBorderWithPrev(true); 2572 } 2573 pCurrPart = pCurrPart->GetFollow(); 2574 } 2575 } 2576 } 2577 2578 void SwTextFormatter::MergeCharacterBorder( SwLinePortion& rPortion, SwLinePortion const *pPrev, SwTextFormatInfo& rInf ) 2579 { 2580 const SwFont aCurFont = *rInf.GetFont(); 2581 if( aCurFont.HasBorder() ) 2582 { 2583 if (pPrev && pPrev->GetJoinBorderWithNext() ) 2584 { 2585 // In some case border merge is called twice to the portion 2586 if( !rPortion.GetJoinBorderWithPrev() ) 2587 { 2588 rPortion.SetJoinBorderWithPrev(true); 2589 if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetLeftBorderSpace() ) 2590 rPortion.Width(rPortion.Width() - aCurFont.GetLeftBorderSpace()); 2591 } 2592 } 2593 else 2594 { 2595 rPortion.SetJoinBorderWithPrev(false); 2596 m_pFirstOfBorderMerge = &rPortion; 2597 } 2598 2599 // Get next portion's font 2600 bool bSeek = false; 2601 if (!rInf.IsFull() && // Not the last portion of the line (in case of line break) 2602 rInf.GetIdx() + rPortion.GetLen() != TextFrameIndex(rInf.GetText().getLength())) // Not the last portion of the paragraph 2603 { 2604 bSeek = Seek(rInf.GetIdx() + rPortion.GetLen()); 2605 } 2606 // Don't join the next portion if SwKernPortion sits between two different boxes. 2607 bool bDisconnect = rPortion.IsKernPortion() && !rPortion.GetJoinBorderWithPrev(); 2608 // If next portion has the same border then merge 2609 if( bSeek && GetFnt()->HasBorder() && ::lcl_HasSameBorder(aCurFont, *GetFnt()) && !bDisconnect ) 2610 { 2611 // In some case border merge is called twice to the portion 2612 if( !rPortion.GetJoinBorderWithNext() ) 2613 { 2614 rPortion.SetJoinBorderWithNext(true); 2615 if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetRightBorderSpace() ) 2616 rPortion.Width(rPortion.Width() - aCurFont.GetRightBorderSpace()); 2617 } 2618 } 2619 // If this is the last portion of the merge group then make the real height merge 2620 else 2621 { 2622 rPortion.SetJoinBorderWithNext(false); 2623 if( m_pFirstOfBorderMerge != &rPortion ) 2624 { 2625 // Calculate maximum height and ascent 2626 SwLinePortion* pActPor = m_pFirstOfBorderMerge; 2627 sal_uInt16 nMaxAscent = 0; 2628 sal_uInt16 nMaxHeight = 0; 2629 bool bReachCurrent = false; 2630 while( pActPor ) 2631 { 2632 if( nMaxHeight < pActPor->Height() ) 2633 nMaxHeight = pActPor->Height(); 2634 if( nMaxAscent < pActPor->GetAscent() ) 2635 nMaxAscent = pActPor->GetAscent(); 2636 2637 pActPor = pActPor->GetNextPortion(); 2638 if( !pActPor && !bReachCurrent ) 2639 { 2640 pActPor = &rPortion; 2641 bReachCurrent = true; 2642 } 2643 } 2644 2645 // Change all portion's height and ascent 2646 pActPor = m_pFirstOfBorderMerge; 2647 bReachCurrent = false; 2648 while( pActPor ) 2649 { 2650 if( nMaxHeight > pActPor->Height() ) 2651 pActPor->Height(nMaxHeight); 2652 if( nMaxAscent > pActPor->GetAscent() ) 2653 pActPor->SetAscent(nMaxAscent); 2654 2655 pActPor = pActPor->GetNextPortion(); 2656 if( !pActPor && !bReachCurrent ) 2657 { 2658 pActPor = &rPortion; 2659 bReachCurrent = true; 2660 } 2661 } 2662 m_pFirstOfBorderMerge = nullptr; 2663 } 2664 } 2665 Seek(rInf.GetIdx()); 2666 } 2667 } 2668 2669 namespace { 2670 // calculates and sets optimal repaint offset for the current line 2671 long lcl_CalcOptRepaint( SwTextFormatter &rThis, 2672 SwLineLayout const &rCurr, 2673 TextFrameIndex const nOldLineEnd, 2674 const std::vector<long> &rFlyStarts ) 2675 { 2676 SwTextFormatInfo& txtFormatInfo = rThis.GetInfo(); 2677 if ( txtFormatInfo.GetIdx() < txtFormatInfo.GetReformatStart() ) 2678 // the reformat position is behind our new line, that means 2679 // something of our text has moved to the next line 2680 return 0; 2681 2682 TextFrameIndex nReformat = std::min(txtFormatInfo.GetReformatStart(), nOldLineEnd); 2683 2684 // in case we do not have any fly in our line, our repaint position 2685 // is the changed position - 1 2686 if ( rFlyStarts.empty() && ! rCurr.IsFly() ) 2687 { 2688 // this is the maximum repaint offset determined during formatting 2689 // for example: the beginning of the first right tab stop 2690 // if this value is 0, this means that we do not have an upper 2691 // limit for the repaint offset 2692 const long nFormatRepaint = txtFormatInfo.GetPaintOfst(); 2693 2694 if (nReformat < txtFormatInfo.GetLineStart() + TextFrameIndex(3)) 2695 return 0; 2696 2697 // step back two positions for smoother repaint 2698 nReformat -= TextFrameIndex(2); 2699 2700 // i#28795, i#34607, i#38388 2701 // step back more characters, this is required by complex scripts 2702 // e.g., for Khmer (thank you, Javier!) 2703 static const TextFrameIndex nMaxContext(10); 2704 if (nReformat > txtFormatInfo.GetLineStart() + nMaxContext) 2705 nReformat = nReformat - nMaxContext; 2706 else 2707 { 2708 nReformat = txtFormatInfo.GetLineStart(); 2709 //reset the margin flag - prevent loops 2710 SwTextCursor::SetRightMargin(false); 2711 } 2712 2713 // Weird situation: Our line used to end with a hole portion 2714 // and we delete some characters at the end of our line. We have 2715 // to take care for repainting the blanks which are not anymore 2716 // covered by the hole portion 2717 while ( nReformat > txtFormatInfo.GetLineStart() && 2718 CH_BLANK == txtFormatInfo.GetChar( nReformat ) ) 2719 --nReformat; 2720 2721 OSL_ENSURE( nReformat < txtFormatInfo.GetIdx(), "Reformat too small for me!" ); 2722 SwRect aRect; 2723 2724 // Note: GetChareRect is not const. It definitely changes the 2725 // bMulti flag. We have to save and restore the old value. 2726 bool bOldMulti = txtFormatInfo.IsMulti(); 2727 rThis.GetCharRect( &aRect, nReformat ); 2728 txtFormatInfo.SetMulti( bOldMulti ); 2729 2730 return nFormatRepaint ? std::min( aRect.Left(), nFormatRepaint ) : 2731 aRect.Left(); 2732 } 2733 else 2734 { 2735 // nReformat may be wrong, if something around flys has changed: 2736 // we compare the former and the new fly positions in this line 2737 // if anything has changed, we carefully have to adjust the right 2738 // repaint position 2739 long nPOfst = 0; 2740 size_t nCnt = 0; 2741 long nX = 0; 2742 TextFrameIndex nIdx = rThis.GetInfo().GetLineStart(); 2743 SwLinePortion* pPor = rCurr.GetFirstPortion(); 2744 2745 while ( pPor ) 2746 { 2747 if ( pPor->IsFlyPortion() ) 2748 { 2749 // compare start of fly with former start of fly 2750 if (nCnt < rFlyStarts.size() && 2751 nX == rFlyStarts[ nCnt ] && 2752 nIdx < nReformat 2753 ) 2754 // found fix position, nothing has changed left from nX 2755 nPOfst = nX + pPor->Width(); 2756 else 2757 break; 2758 2759 nCnt++; 2760 } 2761 nX = nX + pPor->Width(); 2762 nIdx = nIdx + pPor->GetLen(); 2763 pPor = pPor->GetNextPortion(); 2764 } 2765 2766 return nPOfst + rThis.GetLeftMargin(); 2767 } 2768 } 2769 2770 // Determine if we need to build hidden portions 2771 bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex & rPos) 2772 { 2773 // Only if hidden text should not be shown: 2774 // if ( rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar() ) 2775 const bool bShowInDocView = rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar(); 2776 const bool bShowForPrinting = rInf.GetOpt().IsShowHiddenChar( true ) && rInf.GetOpt().IsPrinting(); 2777 if (bShowInDocView || bShowForPrinting) 2778 return false; 2779 2780 const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo(); 2781 TextFrameIndex nHiddenStart; 2782 TextFrameIndex nHiddenEnd; 2783 rSI.GetBoundsOfHiddenRange( rPos, nHiddenStart, nHiddenEnd ); 2784 if ( nHiddenEnd ) 2785 { 2786 rPos = nHiddenEnd; 2787 return true; 2788 } 2789 2790 return false; 2791 } 2792 2793 bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond) 2794 { 2795 return 2796 rFirst.GetTopBorder() == rSecond.GetTopBorder() && 2797 rFirst.GetBottomBorder() == rSecond.GetBottomBorder() && 2798 rFirst.GetLeftBorder() == rSecond.GetLeftBorder() && 2799 rFirst.GetRightBorder() == rSecond.GetRightBorder() && 2800 rFirst.GetTopBorderDist() == rSecond.GetTopBorderDist() && 2801 rFirst.GetBottomBorderDist() == rSecond.GetBottomBorderDist() && 2802 rFirst.GetLeftBorderDist() == rSecond.GetLeftBorderDist() && 2803 rFirst.GetRightBorderDist() == rSecond.GetRightBorderDist() && 2804 rFirst.GetOrientation() == rSecond.GetOrientation() && 2805 rFirst.GetShadowColor() == rSecond.GetShadowColor() && 2806 rFirst.GetShadowWidth() == rSecond.GetShadowWidth() && 2807 rFirst.GetShadowLocation() == rSecond.GetShadowLocation(); 2808 } 2809 2810 } //end unnamed namespace 2811 2812 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 2813
