nsLineLayout.cpp (138441B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* state and methods used while laying out a single line of a block frame */ 8 9 #include "nsLineLayout.h" 10 11 #include <algorithm> 12 13 #include "LayoutLogging.h" 14 #include "RubyUtils.h" 15 #include "mozilla/ComputedStyle.h" 16 #include "mozilla/SVGTextFrame.h" 17 #include "nsBidiPresUtils.h" 18 #include "nsBlockFrame.h" 19 #include "nsContainerFrame.h" 20 #include "nsFloatManager.h" 21 #include "nsFontMetrics.h" 22 #include "nsGkAtoms.h" 23 #include "nsIContent.h" 24 #include "nsLayoutUtils.h" 25 #include "nsPresContext.h" 26 #include "nsRubyFrame.h" 27 #include "nsRubyTextFrame.h" 28 #include "nsStyleConsts.h" 29 #include "nsStyleStructInlines.h" 30 #include "nsTextFrame.h" 31 32 #ifdef DEBUG 33 # undef NOISY_INLINEDIR_ALIGN 34 # undef NOISY_BLOCKDIR_ALIGN 35 # undef NOISY_REFLOW 36 # undef REALLY_NOISY_REFLOW 37 # undef NOISY_PUSHING 38 # undef REALLY_NOISY_PUSHING 39 # undef NOISY_CAN_PLACE_FRAME 40 # undef NOISY_TRIM 41 # undef REALLY_NOISY_TRIM 42 #endif 43 44 using namespace mozilla; 45 46 //---------------------------------------------------------------------- 47 48 nsLineLayout::nsLineLayout(nsPresContext* aPresContext, 49 nsFloatManager* aFloatManager, 50 const ReflowInput& aLineContainerRI, 51 const nsLineList::iterator* aLine, 52 nsLineLayout* aBaseLineLayout) 53 : mPresContext(aPresContext), 54 mFloatManager(aFloatManager), 55 mLineContainerRI(aLineContainerRI), 56 mBaseLineLayout(aBaseLineLayout), 57 mFirstLetterStyleOK(false), 58 mIsTopOfPage(false), 59 mImpactedByFloats(false), 60 mLastFloatWasLetterFrame(false), 61 mLineIsEmpty(false), 62 mLineEndsInBR(false), 63 mNeedBackup(false), 64 mInFirstLine(false), 65 mGotLineBox(false), 66 mInFirstLetter(false), 67 mHasMarker(false), 68 mDirtyNextLine(false), 69 mLineAtStart(false), 70 mHasRuby(false), 71 mSuppressLineWrap(LineContainerFrame()->IsInSVGTextSubtree()), 72 mUsedOverflowWrap(false) { 73 NS_ASSERTION(aFloatManager || LineContainerFrame()->IsLetterFrame(), 74 "float manager should be present"); 75 MOZ_ASSERT( 76 !!mBaseLineLayout == LineContainerFrame()->IsRubyTextContainerFrame(), 77 "Only ruby text container frames have a different base line layout"); 78 MOZ_COUNT_CTOR(nsLineLayout); 79 80 // Stash away some style data that we need 81 nsBlockFrame* blockFrame = do_QueryFrame(LineContainerFrame()); 82 mStyleText = blockFrame ? blockFrame->StyleTextForLineLayout() 83 : LineContainerFrame()->StyleText(); 84 85 mInflationMinFontSize = 86 nsLayoutUtils::InflationMinFontSizeFor(LineContainerFrame()); 87 88 if (aLine) { 89 mGotLineBox = true; 90 mLineBox = *aLine; 91 } 92 } 93 94 void nsLineLayout::BeginLineReflow(nscoord aICoord, nscoord aBCoord, 95 nscoord aISize, nscoord aBSize, 96 bool aImpactedByFloats, bool aIsTopOfPage, 97 WritingMode aWritingMode, 98 const nsSize& aContainerSize, 99 nscoord aInset) { 100 MOZ_ASSERT(nullptr == mRootSpan, "bad linelayout user"); 101 LAYOUT_WARN_IF_FALSE(aISize != NS_UNCONSTRAINEDSIZE, 102 "have unconstrained width; this should only result from " 103 "very large sizes, not attempts at intrinsic width " 104 "calculation"); 105 #ifdef DEBUG 106 if ((aISize != NS_UNCONSTRAINEDSIZE) && ABSURD_SIZE(aISize) && 107 !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) { 108 LineContainerFrame()->ListTag(stdout); 109 printf(": Init: bad caller: width WAS %d(0x%x)\n", aISize, aISize); 110 } 111 if ((aBSize != NS_UNCONSTRAINEDSIZE) && ABSURD_SIZE(aBSize) && 112 !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) { 113 LineContainerFrame()->ListTag(stdout); 114 printf(": Init: bad caller: height WAS %d(0x%x)\n", aBSize, aBSize); 115 } 116 #endif 117 #ifdef NOISY_REFLOW 118 LineContainerFrame()->ListTag(stdout); 119 printf(": BeginLineReflow: %d,%d,%d,%d impacted=%s %s\n", aICoord, aBCoord, 120 aISize, aBSize, aImpactedByFloats ? "true" : "false", 121 aIsTopOfPage ? "top-of-page" : ""); 122 #endif 123 #ifdef DEBUG 124 mSpansAllocated = mSpansFreed = mFramesAllocated = mFramesFreed = 0; 125 #endif 126 127 mFirstLetterStyleOK = false; 128 mIsTopOfPage = aIsTopOfPage; 129 mImpactedByFloats = aImpactedByFloats; 130 mTotalPlacedFrames = 0; 131 if (!mBaseLineLayout) { 132 mLineIsEmpty = true; 133 mLineAtStart = true; 134 } else { 135 mLineIsEmpty = false; 136 mLineAtStart = false; 137 } 138 mLineEndsInBR = false; 139 mSpanDepth = 0; 140 mMaxStartBoxBSize = mMaxEndBoxBSize = 0; 141 142 if (mGotLineBox) { 143 mLineBox->ClearHasMarker(); 144 } 145 146 PerSpanData* psd = NewPerSpanData(); 147 mCurrentSpan = mRootSpan = psd; 148 psd->mReflowInput = &mLineContainerRI; 149 psd->mIStart = aICoord; 150 psd->mICoord = aICoord; 151 psd->mIEnd = aICoord + aISize; 152 // Set up inset to be used for text-wrap:balance implementation, but only if 153 // the available size is greater than inset. 154 psd->mInset = aISize > aInset ? aInset : 0; 155 mContainerSize = aContainerSize; 156 157 mBStartEdge = aBCoord; 158 159 psd->mNoWrap = !mStyleText->WhiteSpaceCanWrapStyle() || mSuppressLineWrap; 160 psd->mWritingMode = aWritingMode; 161 162 // Determine if this is the first line of the block (or first after a hard 163 // line-break, if `each-line` is in effect). 164 nsIFrame* containerFrame = LineContainerFrame(); 165 if (!containerFrame->IsRubyTextContainerFrame()) { 166 bool isFirstLineOrAfterHardBreak = [&] { 167 if (mLineNumber > 0) { 168 return mStyleText->mTextIndent.each_line && GetLine() && 169 !GetLine()->prev()->IsLineWrapped(); 170 } 171 if (nsBlockFrame* prevBlock = 172 do_QueryFrame(containerFrame->GetPrevInFlow())) { 173 return mStyleText->mTextIndent.each_line && 174 (prevBlock->Lines().empty() || 175 !prevBlock->LinesEnd().prev()->IsLineWrapped()); 176 } 177 return true; 178 }(); 179 180 // Resolve and apply the text-indent value if this line requires it. 181 // The `hanging` option inverts which lines are to be indented. 182 if (isFirstLineOrAfterHardBreak != mStyleText->mTextIndent.hanging) { 183 nscoord pctBasis = mLineContainerRI.ComputedISize(); 184 mTextIndent = mStyleText->mTextIndent.length.Resolve(pctBasis); 185 psd->mICoord += mTextIndent; 186 } 187 } 188 189 PerFrameData* pfd = NewPerFrameData(containerFrame); 190 pfd->mAscent = 0; 191 pfd->mSpan = psd; 192 psd->mFrame = pfd; 193 if (containerFrame->IsRubyTextContainerFrame()) { 194 // Ruby text container won't be reflowed via ReflowFrame, hence the 195 // relative positioning information should be recorded here. 196 MOZ_ASSERT(mBaseLineLayout != this); 197 pfd->mIsRelativelyOrStickyPos = 198 mLineContainerRI.mStyleDisplay->IsRelativelyOrStickyPositionedStyle(); 199 if (pfd->mIsRelativelyOrStickyPos) { 200 MOZ_ASSERT(mLineContainerRI.GetWritingMode() == pfd->mWritingMode, 201 "mLineContainerRI.frame == frame, " 202 "hence they should have identical writing mode"); 203 pfd->mOffsets = 204 mLineContainerRI.ComputedLogicalOffsets(pfd->mWritingMode); 205 } 206 } 207 } 208 209 bool nsLineLayout::EndLineReflow() { 210 #ifdef NOISY_REFLOW 211 LineContainerFrame()->ListTag(stdout); 212 printf(": EndLineReflow: width=%d\n", 213 mRootSpan->mICoord - mRootSpan->mIStart); 214 #endif 215 216 NS_ASSERTION(!mBaseLineLayout || 217 (!mSpansAllocated && !mSpansFreed && !mSpanFreeList && 218 !mFramesAllocated && !mFramesFreed && !mFrameFreeList), 219 "Allocated frames or spans on non-base line layout?"); 220 MOZ_ASSERT(mRootSpan == mCurrentSpan); 221 222 UnlinkFrame(mRootSpan->mFrame); 223 mCurrentSpan = mRootSpan = nullptr; 224 225 NS_ASSERTION(mSpansAllocated == mSpansFreed, "leak"); 226 NS_ASSERTION(mFramesAllocated == mFramesFreed, "leak"); 227 228 #if 0 229 static int32_t maxSpansAllocated = NS_LINELAYOUT_NUM_SPANS; 230 static int32_t maxFramesAllocated = NS_LINELAYOUT_NUM_FRAMES; 231 if (mSpansAllocated > maxSpansAllocated) { 232 printf("XXX: saw a line with %d spans\n", mSpansAllocated); 233 maxSpansAllocated = mSpansAllocated; 234 } 235 if (mFramesAllocated > maxFramesAllocated) { 236 printf("XXX: saw a line with %d frames\n", mFramesAllocated); 237 maxFramesAllocated = mFramesAllocated; 238 } 239 #endif 240 241 return mUsedOverflowWrap; 242 } 243 244 // XXX swtich to a single mAvailLineWidth that we adjust as each frame 245 // on the line is placed. Each span can still have a per-span mICoord that 246 // tracks where a child frame is going in its span; they don't need a 247 // per-span mIStart? 248 249 void nsLineLayout::UpdateBand(WritingMode aWM, 250 const LogicalRect& aNewAvailSpace, 251 nsIFrame* aFloatFrame) { 252 WritingMode lineWM = mRootSpan->mWritingMode; 253 // need to convert to our writing mode, because we might have a different 254 // mode from the caller due to dir: auto 255 LogicalRect availSpace = 256 aNewAvailSpace.ConvertTo(lineWM, aWM, ContainerSize()); 257 #ifdef REALLY_NOISY_REFLOW 258 printf( 259 "nsLL::UpdateBand %d, %d, %d, %d, (converted to %d, %d, %d, %d); " 260 "frame=%p\n will set mImpacted to true\n", 261 aNewAvailSpace.IStart(aWM), aNewAvailSpace.BStart(aWM), 262 aNewAvailSpace.ISize(aWM), aNewAvailSpace.BSize(aWM), 263 availSpace.IStart(lineWM), availSpace.BStart(lineWM), 264 availSpace.ISize(lineWM), availSpace.BSize(lineWM), aFloatFrame); 265 #endif 266 #ifdef DEBUG 267 if ((availSpace.ISize(lineWM) != NS_UNCONSTRAINEDSIZE) && 268 ABSURD_SIZE(availSpace.ISize(lineWM)) && 269 !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) { 270 LineContainerFrame()->ListTag(stdout); 271 printf(": UpdateBand: bad caller: ISize WAS %d(0x%x)\n", 272 availSpace.ISize(lineWM), availSpace.ISize(lineWM)); 273 } 274 if ((availSpace.BSize(lineWM) != NS_UNCONSTRAINEDSIZE) && 275 ABSURD_SIZE(availSpace.BSize(lineWM)) && 276 !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) { 277 LineContainerFrame()->ListTag(stdout); 278 printf(": UpdateBand: bad caller: BSize WAS %d(0x%x)\n", 279 availSpace.BSize(lineWM), availSpace.BSize(lineWM)); 280 } 281 #endif 282 283 // Compute the difference between last times width and the new width 284 NS_WARNING_ASSERTION( 285 mRootSpan->mIEnd != NS_UNCONSTRAINEDSIZE && 286 availSpace.ISize(lineWM) != NS_UNCONSTRAINEDSIZE, 287 "have unconstrained inline size; this should only result from very large " 288 "sizes, not attempts at intrinsic width calculation"); 289 // The root span's mIStart moves to aICoord 290 nscoord deltaICoord = availSpace.IStart(lineWM) - mRootSpan->mIStart; 291 // The inline size of all spans changes by this much (the root span's 292 // mIEnd moves to aICoord + aISize, its new inline size is aISize) 293 nscoord deltaISize = 294 availSpace.ISize(lineWM) - (mRootSpan->mIEnd - mRootSpan->mIStart); 295 #ifdef NOISY_REFLOW 296 LineContainerFrame()->ListTag(stdout); 297 printf(": UpdateBand: %d,%d,%d,%d deltaISize=%d deltaICoord=%d\n", 298 availSpace.IStart(lineWM), availSpace.BStart(lineWM), 299 availSpace.ISize(lineWM), availSpace.BSize(lineWM), deltaISize, 300 deltaICoord); 301 #endif 302 303 // Update the root span position 304 mRootSpan->mIStart += deltaICoord; 305 mRootSpan->mIEnd += deltaICoord; 306 mRootSpan->mICoord += deltaICoord; 307 308 // Now update the right edges of the open spans to account for any 309 // change in available space width 310 for (PerSpanData* psd = mCurrentSpan; psd; psd = psd->mParent) { 311 psd->mIEnd += deltaISize; 312 psd->mContainsFloat = true; 313 #ifdef NOISY_REFLOW 314 printf(" span %p: oldIEnd=%d newIEnd=%d\n", psd, psd->mIEnd - deltaISize, 315 psd->mIEnd); 316 #endif 317 } 318 NS_ASSERTION(mRootSpan->mContainsFloat && 319 mRootSpan->mIStart == availSpace.IStart(lineWM) && 320 mRootSpan->mIEnd == availSpace.IEnd(lineWM), 321 "root span was updated incorrectly?"); 322 323 // Update frame bounds 324 // Note: Only adjust the outermost frames (the ones that are direct 325 // children of the block), not the ones in the child spans. The reason 326 // is simple: the frames in the spans have coordinates local to their 327 // parent therefore they are moved when their parent span is moved. 328 if (deltaICoord != 0) { 329 for (PerFrameData* pfd = mRootSpan->mFirstFrame; pfd; pfd = pfd->mNext) { 330 pfd->mBounds.IStart(lineWM) += deltaICoord; 331 } 332 } 333 334 mBStartEdge = availSpace.BStart(lineWM); 335 mImpactedByFloats = true; 336 337 mLastFloatWasLetterFrame = aFloatFrame->IsLetterFrame(); 338 } 339 340 nsLineLayout::PerSpanData* nsLineLayout::NewPerSpanData() { 341 nsLineLayout* outerLineLayout = GetOutermostLineLayout(); 342 PerSpanData* psd = outerLineLayout->mSpanFreeList; 343 if (!psd) { 344 void* mem = outerLineLayout->mArena.Allocate(sizeof(PerSpanData)); 345 psd = reinterpret_cast<PerSpanData*>(mem); 346 } else { 347 outerLineLayout->mSpanFreeList = psd->mNextFreeSpan; 348 } 349 psd->mParent = nullptr; 350 psd->mFrame = nullptr; 351 psd->mFirstFrame = nullptr; 352 psd->mLastFrame = nullptr; 353 psd->mReflowInput = nullptr; 354 psd->mContainsFloat = false; 355 psd->mHasNonemptyContent = false; 356 psd->mBaseline = nullptr; 357 358 #ifdef DEBUG 359 outerLineLayout->mSpansAllocated++; 360 #endif 361 return psd; 362 } 363 364 void nsLineLayout::BeginSpan(nsIFrame* aFrame, 365 const ReflowInput* aSpanReflowInput, 366 nscoord aIStart, nscoord aIEnd, 367 nscoord* aBaseline) { 368 NS_ASSERTION(aIEnd != NS_UNCONSTRAINEDSIZE, 369 "should no longer be using unconstrained sizes"); 370 #ifdef NOISY_REFLOW 371 nsIFrame::IndentBy(stdout, mSpanDepth + 1); 372 aFrame->ListTag(stdout); 373 printf(": BeginSpan leftEdge=%d rightEdge=%d\n", aIStart, aIEnd); 374 #endif 375 376 PerSpanData* psd = NewPerSpanData(); 377 // Link up span frame's pfd to point to its child span data 378 PerFrameData* pfd = mCurrentSpan->mLastFrame; 379 NS_ASSERTION(pfd->mFrame == aFrame, "huh?"); 380 pfd->mSpan = psd; 381 382 // Init new span 383 psd->mFrame = pfd; 384 psd->mParent = mCurrentSpan; 385 psd->mReflowInput = aSpanReflowInput; 386 psd->mIStart = aIStart; 387 psd->mICoord = aIStart; 388 psd->mIEnd = aIEnd; 389 psd->mInset = 0; // inset applies only to the root span 390 psd->mBaseline = aBaseline; 391 392 nsIFrame* frame = aSpanReflowInput->mFrame; 393 psd->mNoWrap = !frame->StyleText()->WhiteSpaceCanWrap(frame) || 394 mSuppressLineWrap || frame->Style()->ShouldSuppressLineBreak(); 395 psd->mWritingMode = aSpanReflowInput->GetWritingMode(); 396 397 // Switch to new span 398 mCurrentSpan = psd; 399 mSpanDepth++; 400 } 401 402 nscoord nsLineLayout::EndSpan(nsIFrame* aFrame) { 403 NS_ASSERTION(mSpanDepth > 0, "end-span without begin-span"); 404 #ifdef NOISY_REFLOW 405 nsIFrame::IndentBy(stdout, mSpanDepth); 406 aFrame->ListTag(stdout); 407 printf(": EndSpan width=%d\n", mCurrentSpan->mICoord - mCurrentSpan->mIStart); 408 #endif 409 PerSpanData* psd = mCurrentSpan; 410 MOZ_ASSERT(psd->mParent, "We never call this on the root"); 411 412 if (psd->mNoWrap && !psd->mParent->mNoWrap) { 413 FlushNoWrapFloats(); 414 } 415 416 nscoord iSizeResult = psd->mLastFrame ? (psd->mICoord - psd->mIStart) : 0; 417 418 mSpanDepth--; 419 mCurrentSpan->mReflowInput = nullptr; // no longer valid so null it out! 420 mCurrentSpan = mCurrentSpan->mParent; 421 return iSizeResult; 422 } 423 424 void nsLineLayout::AttachFrameToBaseLineLayout(PerFrameData* aFrame) { 425 MOZ_ASSERT(mBaseLineLayout, 426 "This method must not be called in a base line layout."); 427 428 PerFrameData* baseFrame = mBaseLineLayout->LastFrame(); 429 MOZ_ASSERT(aFrame && baseFrame); 430 MOZ_ASSERT(!aFrame->mIsLinkedToBase, 431 "The frame must not have been linked with the base"); 432 #ifdef DEBUG 433 LayoutFrameType baseType = baseFrame->mFrame->Type(); 434 LayoutFrameType annotationType = aFrame->mFrame->Type(); 435 MOZ_ASSERT((baseType == LayoutFrameType::RubyBaseContainer && 436 annotationType == LayoutFrameType::RubyTextContainer) || 437 (baseType == LayoutFrameType::RubyBase && 438 annotationType == LayoutFrameType::RubyText)); 439 #endif 440 441 aFrame->mNextAnnotation = baseFrame->mNextAnnotation; 442 baseFrame->mNextAnnotation = aFrame; 443 aFrame->mIsLinkedToBase = true; 444 } 445 446 int32_t nsLineLayout::GetCurrentSpanCount() const { 447 MOZ_ASSERT(mCurrentSpan == mRootSpan, "bad linelayout user"); 448 int32_t count = 0; 449 for (const auto* pfd = mRootSpan->mFirstFrame; pfd; pfd = pfd->mNext) { 450 count++; 451 } 452 return count; 453 } 454 455 void nsLineLayout::SplitLineTo(int32_t aNewCount) { 456 MOZ_ASSERT(mCurrentSpan == mRootSpan, "bad linelayout user"); 457 458 #ifdef REALLY_NOISY_PUSHING 459 printf("SplitLineTo %d (current count=%d); before:\n", aNewCount, 460 GetCurrentSpanCount()); 461 DumpPerSpanData(mRootSpan, 1); 462 #endif 463 for (auto* pfd = mRootSpan->mFirstFrame; pfd; pfd = pfd->mNext) { 464 if (--aNewCount == 0) { 465 // Truncate list at pfd (we keep pfd, but anything following is freed) 466 PerFrameData* next = pfd->mNext; 467 pfd->mNext = nullptr; 468 mRootSpan->mLastFrame = pfd; 469 470 // Now unlink all of the frames following pfd 471 UnlinkFrame(next); 472 break; 473 } 474 } 475 #ifdef NOISY_PUSHING 476 printf("SplitLineTo %d (current count=%d); after:\n", aNewCount, 477 GetCurrentSpanCount()); 478 DumpPerSpanData(mRootSpan, 1); 479 #endif 480 } 481 482 void nsLineLayout::PushFrame(nsIFrame* aFrame) { 483 PerSpanData* psd = mCurrentSpan; 484 NS_ASSERTION(psd->mLastFrame->mFrame == aFrame, "pushing non-last frame"); 485 486 #ifdef REALLY_NOISY_PUSHING 487 nsIFrame::IndentBy(stdout, mSpanDepth); 488 printf("PushFrame %p, before:\n", psd); 489 DumpPerSpanData(psd, 1); 490 #endif 491 492 // Take the last frame off of the span's frame list 493 PerFrameData* pfd = psd->mLastFrame; 494 if (pfd == psd->mFirstFrame) { 495 // We are pushing away the only frame...empty the list 496 psd->mFirstFrame = nullptr; 497 psd->mLastFrame = nullptr; 498 } else { 499 PerFrameData* prevFrame = pfd->mPrev; 500 prevFrame->mNext = nullptr; 501 psd->mLastFrame = prevFrame; 502 } 503 504 // Now unlink the frame 505 MOZ_ASSERT(!pfd->mNext); 506 UnlinkFrame(pfd); 507 #ifdef NOISY_PUSHING 508 nsIFrame::IndentBy(stdout, mSpanDepth); 509 printf("PushFrame: %p after:\n", psd); 510 DumpPerSpanData(psd, 1); 511 #endif 512 } 513 514 void nsLineLayout::UnlinkFrame(PerFrameData* pfd) { 515 while (nullptr != pfd) { 516 PerFrameData* next = pfd->mNext; 517 if (pfd->mIsLinkedToBase) { 518 // This frame is linked to a ruby base, and should not be freed 519 // now. Just unlink it from the span. It will be freed when its 520 // base frame gets unlinked. 521 pfd->mNext = pfd->mPrev = nullptr; 522 pfd = next; 523 continue; 524 } 525 526 // It is a ruby base frame. If there are any annotations 527 // linked to this frame, free them first. 528 PerFrameData* annotationPFD = pfd->mNextAnnotation; 529 while (annotationPFD) { 530 PerFrameData* nextAnnotation = annotationPFD->mNextAnnotation; 531 MOZ_ASSERT( 532 annotationPFD->mNext == nullptr && annotationPFD->mPrev == nullptr, 533 "PFD in annotations should have been unlinked."); 534 FreeFrame(annotationPFD); 535 annotationPFD = nextAnnotation; 536 } 537 538 FreeFrame(pfd); 539 pfd = next; 540 } 541 } 542 543 void nsLineLayout::FreeFrame(PerFrameData* pfd) { 544 if (nullptr != pfd->mSpan) { 545 FreeSpan(pfd->mSpan); 546 } 547 nsLineLayout* outerLineLayout = GetOutermostLineLayout(); 548 pfd->mNext = outerLineLayout->mFrameFreeList; 549 outerLineLayout->mFrameFreeList = pfd; 550 #ifdef DEBUG 551 outerLineLayout->mFramesFreed++; 552 #endif 553 } 554 555 void nsLineLayout::FreeSpan(PerSpanData* psd) { 556 // Unlink its frames 557 UnlinkFrame(psd->mFirstFrame); 558 559 nsLineLayout* outerLineLayout = GetOutermostLineLayout(); 560 // Now put the span on the free list since it's free too 561 psd->mNextFreeSpan = outerLineLayout->mSpanFreeList; 562 outerLineLayout->mSpanFreeList = psd; 563 #ifdef DEBUG 564 outerLineLayout->mSpansFreed++; 565 #endif 566 } 567 568 bool nsLineLayout::IsZeroBSize() const { 569 for (const auto* pfd = mCurrentSpan->mFirstFrame; pfd; pfd = pfd->mNext) { 570 if (0 != pfd->mBounds.BSize(mCurrentSpan->mWritingMode)) { 571 return false; 572 } 573 } 574 return true; 575 } 576 577 nsLineLayout::PerFrameData* nsLineLayout::NewPerFrameData(nsIFrame* aFrame) { 578 nsLineLayout* outerLineLayout = GetOutermostLineLayout(); 579 PerFrameData* pfd = outerLineLayout->mFrameFreeList; 580 if (!pfd) { 581 void* mem = outerLineLayout->mArena.Allocate(sizeof(PerFrameData)); 582 pfd = reinterpret_cast<PerFrameData*>(mem); 583 } else { 584 outerLineLayout->mFrameFreeList = pfd->mNext; 585 } 586 pfd->mSpan = nullptr; 587 pfd->mNext = nullptr; 588 pfd->mPrev = nullptr; 589 pfd->mNextAnnotation = nullptr; 590 pfd->mFrame = aFrame; 591 592 // all flags default to false 593 pfd->mIsRelativelyOrStickyPos = false; 594 pfd->mIsTextFrame = false; 595 pfd->mIsNonEmptyTextFrame = false; 596 pfd->mIsNonWhitespaceTextFrame = false; 597 pfd->mIsLetterFrame = false; 598 pfd->mRecomputeOverflow = false; 599 pfd->mIsMarker = false; 600 pfd->mSkipWhenTrimmingWhitespace = false; 601 pfd->mIsEmpty = false; 602 pfd->mIsPlaceholder = false; 603 pfd->mIsLinkedToBase = false; 604 605 pfd->mWritingMode = aFrame->GetWritingMode(); 606 WritingMode lineWM = mRootSpan->mWritingMode; 607 pfd->mBounds = LogicalRect(lineWM); 608 pfd->mOverflowAreas.Clear(); 609 pfd->mMargin = LogicalMargin(lineWM); 610 pfd->mBorderPadding = LogicalMargin(lineWM); 611 pfd->mOffsets = LogicalMargin(pfd->mWritingMode); 612 613 pfd->mJustificationInfo = JustificationInfo(); 614 pfd->mJustificationAssignment = JustificationAssignment(); 615 616 #ifdef DEBUG 617 pfd->mBlockDirAlign = 0xFF; 618 outerLineLayout->mFramesAllocated++; 619 #endif 620 return pfd; 621 } 622 623 // Checks all four sides for percentage units. This means it should 624 // only be used for things (margin, padding) where percentages on top 625 // and bottom depend on the *width* just like percentages on left and 626 // right. 627 template <typename T> 628 static bool HasPercentageUnitSide(const StyleRect<T>& aSides) { 629 return aSides.Any([](const auto& aLength) { return aLength.HasPercent(); }); 630 } 631 632 static bool HasPercentageUnitMargin(const nsStyleMargin& aStyleMargin, 633 const AnchorPosResolutionParams& aParams) { 634 for (const auto side : AllPhysicalSides()) { 635 if (aStyleMargin.GetMargin(side, aParams)->HasPercent()) { 636 return true; 637 } 638 } 639 return false; 640 } 641 642 static bool IsPercentageAware(const nsIFrame* aFrame, WritingMode aWM) { 643 MOZ_ASSERT(aFrame, "null frame is not allowed"); 644 645 LayoutFrameType fType = aFrame->Type(); 646 if (fType == LayoutFrameType::Text) { 647 // None of these things can ever be true for text frames. 648 return false; 649 } 650 651 // Some of these things don't apply to non-replaced inline frames 652 // (that is, fType == LayoutFrameType::Inline), but we won't bother making 653 // things unnecessarily complicated, since they'll probably be set 654 // quite rarely. 655 656 const nsStyleMargin* margin = aFrame->StyleMargin(); 657 const auto anchorResolutionParams = AnchorPosResolutionParams::From(aFrame); 658 if (HasPercentageUnitMargin(*margin, anchorResolutionParams)) { 659 return true; 660 } 661 662 const nsStylePadding* padding = aFrame->StylePadding(); 663 if (HasPercentageUnitSide(padding->mPadding)) { 664 return true; 665 } 666 667 // Note that borders can't be aware of percentages 668 669 const nsStylePosition* pos = aFrame->StylePosition(); 670 const auto iSize = pos->ISize(aWM, anchorResolutionParams); 671 const auto anchorOffsetResolutionParams = 672 AnchorPosOffsetResolutionParams::UseCBFrameSize(anchorResolutionParams); 673 if ((nsStylePosition::ISizeDependsOnContainer(iSize) && !iSize->IsAuto()) || 674 nsStylePosition::MaxISizeDependsOnContainer( 675 pos->MaxISize(aWM, anchorResolutionParams)) || 676 nsStylePosition::MinISizeDependsOnContainer( 677 pos->MinISize(aWM, anchorResolutionParams)) || 678 pos->GetAnchorResolvedInset(LogicalSide::IStart, aWM, 679 anchorOffsetResolutionParams) 680 ->HasPercent() || 681 pos->GetAnchorResolvedInset(LogicalSide::IEnd, aWM, 682 anchorOffsetResolutionParams) 683 ->HasPercent()) { 684 return true; 685 } 686 687 if (iSize->IsAuto()) { 688 // We need to check for frames that shrink-wrap when they're auto 689 // width. 690 const nsStyleDisplay* disp = aFrame->StyleDisplay(); 691 if ((disp->DisplayOutside() == StyleDisplayOutside::Inline && 692 (disp->DisplayInside() == StyleDisplayInside::FlowRoot || 693 disp->DisplayInside() == StyleDisplayInside::Table)) || 694 fType == LayoutFrameType::FieldSet) { 695 return true; 696 } 697 698 // Per CSS 2.1, section 10.3.2: 699 // If 'height' and 'width' both have computed values of 'auto' and 700 // the element has an intrinsic ratio but no intrinsic height or 701 // width and the containing block's width does not itself depend 702 // on the replaced element's width, then the used value of 'width' 703 // is calculated from the constraint equation used for 704 // block-level, non-replaced elements in normal flow. 705 nsIFrame* f = const_cast<nsIFrame*>(aFrame); 706 if (f->GetAspectRatio() && 707 // Some percents are treated like 'auto', so check != coord 708 !pos->BSize(aWM, anchorResolutionParams)->ConvertsToLength()) { 709 const IntrinsicSize& intrinsicSize = f->GetIntrinsicSize(); 710 if (!intrinsicSize.width && !intrinsicSize.height) { 711 return true; 712 } 713 } 714 } 715 716 return false; 717 } 718 719 void nsLineLayout::ReflowFrame(nsIFrame* aFrame, nsReflowStatus& aReflowStatus, 720 ReflowOutput* aMetrics, bool& aPushedFrame) { 721 // Initialize OUT parameter 722 aPushedFrame = false; 723 724 PerFrameData* pfd = NewPerFrameData(aFrame); 725 PerSpanData* psd = mCurrentSpan; 726 psd->AppendFrame(pfd); 727 728 #ifdef REALLY_NOISY_REFLOW 729 nsIFrame::IndentBy(stdout, mSpanDepth); 730 printf("%p: Begin ReflowFrame pfd=%p ", psd, pfd); 731 aFrame->ListTag(stdout); 732 printf("\n"); 733 #endif 734 735 if (mCurrentSpan == mRootSpan) { 736 pfd->mFrame->RemoveProperty(nsIFrame::LineBaselineOffset()); 737 } else { 738 #ifdef DEBUG 739 bool hasLineOffset; 740 pfd->mFrame->GetProperty(nsIFrame::LineBaselineOffset(), &hasLineOffset); 741 NS_ASSERTION(!hasLineOffset, 742 "LineBaselineOffset was set but was not expected"); 743 #endif 744 } 745 746 mJustificationInfo = JustificationInfo(); 747 748 // Stash copies of some of the computed state away for later 749 // (block-direction alignment, for example) 750 WritingMode frameWM = pfd->mWritingMode; 751 WritingMode lineWM = mRootSpan->mWritingMode; 752 753 // NOTE: While the inline direction coordinate remains relative to the 754 // parent span, the block direction coordinate is fixed at the top 755 // edge for the line. During VerticalAlignFrames we will repair this 756 // so that the block direction coordinate is properly set and relative 757 // to the appropriate span. 758 pfd->mBounds.IStart(lineWM) = psd->mICoord; 759 pfd->mBounds.BStart(lineWM) = mBStartEdge; 760 761 // We want to guarantee that we always make progress when 762 // formatting. Therefore, if the object being placed on the line is 763 // too big for the line, but it is the only thing on the line and is not 764 // impacted by a float, then we go ahead and place it anyway. (If the line 765 // is impacted by one or more floats, then it is safe to break because 766 // we can move the line down below float(s).) 767 // 768 // Capture this state *before* we reflow the frame in case it clears 769 // the state out. We need to know how to treat the current frame 770 // when breaking. 771 bool notSafeToBreak = LineIsEmpty() && !mImpactedByFloats; 772 773 // Figure out whether we're talking about a textframe here 774 LayoutFrameType frameType = aFrame->Type(); 775 const bool isText = frameType == LayoutFrameType::Text; 776 777 // Inline-ish and text-ish things don't compute their width; 778 // everything else does. We need to give them an available width that 779 // reflects the space left on the line. 780 LAYOUT_WARN_IF_FALSE(psd->mIEnd != NS_UNCONSTRAINEDSIZE, 781 "have unconstrained width; this should only result from " 782 "very large sizes, not attempts at intrinsic width " 783 "calculation"); 784 nscoord availableSpaceOnLine = psd->mIEnd - psd->mICoord - psd->mInset; 785 786 // Setup reflow input for reflowing the frame 787 Maybe<ReflowInput> reflowInputHolder; 788 if (!isText) { 789 // Compute the available size for the frame. This available width 790 // includes room for the side margins. 791 // For now, set the available block-size to unconstrained always. 792 LogicalSize availSize = mLineContainerRI.ComputedSize(frameWM); 793 availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE; 794 reflowInputHolder.emplace(mPresContext, *psd->mReflowInput, aFrame, 795 availSize); 796 ReflowInput& reflowInput = *reflowInputHolder; 797 reflowInput.mLineLayout = this; 798 reflowInput.mFlags.mIsTopOfPage = mIsTopOfPage; 799 if (reflowInput.ComputedISize() == NS_UNCONSTRAINEDSIZE) { 800 reflowInput.SetAvailableISize(availableSpaceOnLine); 801 } 802 pfd->mMargin = reflowInput.ComputedLogicalMargin(lineWM); 803 pfd->mBorderPadding = reflowInput.ComputedLogicalBorderPadding(lineWM); 804 pfd->mIsRelativelyOrStickyPos = 805 reflowInput.mStyleDisplay->IsRelativelyOrStickyPositionedStyle(); 806 if (pfd->mIsRelativelyOrStickyPos) { 807 pfd->mOffsets = reflowInput.ComputedLogicalOffsets(frameWM); 808 } 809 810 // Calculate whether the the frame should have a start margin and 811 // subtract the margin from the available width if necessary. 812 // The margin will be applied to the starting inline coordinates of 813 // the frame in CanPlaceFrame() after reflowing the frame. 814 AllowForStartMargin(pfd, reflowInput); 815 } 816 // if isText(), no need to propagate NS_FRAME_IS_DIRTY from the parent, 817 // because reflow doesn't look at the dirty bits on the frame being reflowed. 818 819 // See if this frame depends on the inline-size of its containing block. 820 // If so, disable resize reflow optimizations for the line. (Note that, 821 // to be conservative, we do this if we *try* to fit a frame on a 822 // line, even if we don't succeed.) (Note also that we can only make 823 // this IsPercentageAware check *after* we've constructed our 824 // ReflowInput, because that construction may be what forces aFrame 825 // to lazily initialize its (possibly-percent-valued) intrinsic size.) 826 if (mGotLineBox && IsPercentageAware(aFrame, lineWM)) { 827 mLineBox->DisableResizeReflowOptimization(); 828 } 829 830 // Note that we don't bother positioning the frame yet, because we're probably 831 // going to end up moving it when we do the block-direction alignment. 832 833 // Adjust float manager coordinate system for the frame. 834 ReflowOutput reflowOutput(lineWM); 835 #ifdef DEBUG 836 reflowOutput.ISize(lineWM) = nscoord(0xdeadbeef); 837 reflowOutput.BSize(lineWM) = nscoord(0xdeadbeef); 838 #endif 839 nscoord tI = pfd->mBounds.LineLeft(lineWM, ContainerSize()); 840 nscoord tB = pfd->mBounds.BStart(lineWM); 841 mFloatManager->Translate(tI, tB); 842 843 int32_t savedOptionalBreakOffset; 844 gfxBreakPriority savedOptionalBreakPriority; 845 nsIFrame* savedOptionalBreakFrame = GetLastOptionalBreakPosition( 846 &savedOptionalBreakOffset, &savedOptionalBreakPriority); 847 848 if (!isText) { 849 aFrame->Reflow(mPresContext, reflowOutput, *reflowInputHolder, 850 aReflowStatus); 851 } else { 852 static_cast<nsTextFrame*>(aFrame)->ReflowText( 853 *this, availableSpaceOnLine, 854 psd->mReflowInput->mRenderingContext->GetDrawTarget(), reflowOutput, 855 aReflowStatus); 856 } 857 858 pfd->mJustificationInfo = mJustificationInfo; 859 mJustificationInfo = JustificationInfo(); 860 861 // See if the frame is a placeholderFrame and if it is process 862 // the float. At the same time, check if the frame has any non-collapsed-away 863 // content. 864 bool placedFloat = false; 865 bool isEmpty; 866 if (frameType == LayoutFrameType::None) { 867 isEmpty = pfd->mFrame->IsEmpty(); 868 } else if (LayoutFrameType::Placeholder == frameType) { 869 isEmpty = true; 870 pfd->mIsPlaceholder = true; 871 pfd->mSkipWhenTrimmingWhitespace = true; 872 nsIFrame* outOfFlowFrame = nsLayoutUtils::GetFloatFromPlaceholder(aFrame); 873 if (outOfFlowFrame) { 874 if (psd->mNoWrap && 875 // We can always place floats in an empty line. 876 !LineIsEmpty() && 877 // We always place floating letter frames. This kinda sucks. They'd 878 // usually fall into the LineIsEmpty() check anyway, except when 879 // there's something like a ::marker before or what not. We actually 880 // need to place them now, because they're pretty nasty and they 881 // create continuations that are in flow and not a kid of the 882 // previous continuation's parent. We don't want the deferred reflow 883 // of the letter frame to kill a continuation after we've stored it 884 // in the line layout data structures. See bug 1490281 to fix the 885 // underlying issue. When that's fixed this check should be removed. 886 !outOfFlowFrame->IsLetterFrame() && 887 !GetOutermostLineLayout()->mBlockRS->mFlags.mCanHaveOverflowMarkers) { 888 // We'll do this at the next break opportunity. 889 RecordNoWrapFloat(outOfFlowFrame); 890 } else { 891 placedFloat = TryToPlaceFloat(outOfFlowFrame); 892 } 893 } 894 } else if (isText) { 895 // Note non-empty text-frames for inline frame compatibility hackery 896 pfd->mIsTextFrame = true; 897 auto* textFrame = static_cast<nsTextFrame*>(pfd->mFrame); 898 isEmpty = !textFrame->HasNoncollapsedCharacters(); 899 if (!isEmpty) { 900 pfd->mIsNonEmptyTextFrame = true; 901 pfd->mIsNonWhitespaceTextFrame = 902 !textFrame->GetContent()->TextIsOnlyWhitespace(); 903 } 904 } else if (LayoutFrameType::Br == frameType) { 905 pfd->mSkipWhenTrimmingWhitespace = true; 906 isEmpty = false; 907 } else { 908 if (LayoutFrameType::Letter == frameType) { 909 pfd->mIsLetterFrame = true; 910 } 911 if (pfd->mSpan) { 912 isEmpty = !pfd->mSpan->mHasNonemptyContent && pfd->mFrame->IsSelfEmpty(); 913 } else { 914 isEmpty = pfd->mFrame->IsEmpty(); 915 } 916 } 917 pfd->mIsEmpty = isEmpty; 918 919 mFloatManager->Translate(-tI, -tB); 920 921 NS_ASSERTION(reflowOutput.ISize(lineWM) >= 0, "bad inline size"); 922 NS_ASSERTION(reflowOutput.BSize(lineWM) >= 0, "bad block size"); 923 if (reflowOutput.ISize(lineWM) < 0) { 924 reflowOutput.ISize(lineWM) = 0; 925 } 926 if (reflowOutput.BSize(lineWM) < 0) { 927 reflowOutput.BSize(lineWM) = 0; 928 } 929 930 #ifdef DEBUG 931 // Note: break-before means ignore the reflow metrics since the 932 // frame will be reflowed another time. 933 if (!aReflowStatus.IsInlineBreakBefore()) { 934 if ((ABSURD_SIZE(reflowOutput.ISize(lineWM)) || 935 ABSURD_SIZE(reflowOutput.BSize(lineWM))) && 936 !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) { 937 printf("nsLineLayout: "); 938 aFrame->ListTag(stdout); 939 printf(" metrics=%d,%d!\n", reflowOutput.Width(), reflowOutput.Height()); 940 } 941 if ((reflowOutput.Width() == nscoord(0xdeadbeef)) || 942 (reflowOutput.Height() == nscoord(0xdeadbeef))) { 943 printf("nsLineLayout: "); 944 aFrame->ListTag(stdout); 945 printf(" didn't set w/h %d,%d!\n", reflowOutput.Width(), 946 reflowOutput.Height()); 947 } 948 } 949 #endif 950 951 // Unlike with non-inline reflow, the overflow area here does *not* 952 // include the accumulation of the frame's bounds and its inline 953 // descendants' bounds. Nor does it include the outline area; it's 954 // just the union of the bounds of any absolute children. That is 955 // added in later by nsLineLayout::ReflowInlineFrames. 956 pfd->mOverflowAreas = reflowOutput.mOverflowAreas; 957 958 pfd->mBounds.ISize(lineWM) = reflowOutput.ISize(lineWM); 959 pfd->mBounds.BSize(lineWM) = reflowOutput.BSize(lineWM); 960 961 // Size the frame, but |RelativePositionFrames| will size the view. 962 aFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd)); 963 964 // Tell the frame that we're done reflowing it 965 aFrame->DidReflow(mPresContext, isText ? nullptr : reflowInputHolder.ptr()); 966 967 if (aMetrics) { 968 *aMetrics = reflowOutput; 969 } 970 971 if (!aReflowStatus.IsInlineBreakBefore()) { 972 // If frame is complete and has a next-in-flow, we need to delete 973 // them now. Do not do this when a break-before is signaled because 974 // the frame is going to get reflowed again (and may end up wanting 975 // a next-in-flow where it ends up). 976 if (aReflowStatus.IsComplete()) { 977 if (nsIFrame* kidNextInFlow = aFrame->GetNextInFlow()) { 978 // Remove all of the childs next-in-flows. Make sure that we ask 979 // the right parent to do the removal (it's possible that the 980 // parent is not this because we are executing pullup code) 981 FrameDestroyContext context(aFrame->PresShell()); 982 kidNextInFlow->GetParent()->DeleteNextInFlowChild(context, 983 kidNextInFlow, true); 984 } 985 } 986 987 // Check whether this frame breaks up text runs. All frames break up text 988 // runs (hence return false here) except for text frames and inline 989 // containers. 990 bool continuingTextRun = aFrame->CanContinueTextRun(); 991 992 // Clear any residual mTrimmableISize if this isn't a text frame 993 if (!continuingTextRun && !pfd->mSkipWhenTrimmingWhitespace) { 994 mTrimmableISize = 0; 995 } 996 997 // See if we can place the frame. If we can't fit it, then we 998 // return now. 999 bool optionalBreakAfterFits; 1000 NS_ASSERTION(isText || !reflowInputHolder->mStyleDisplay->IsFloating( 1001 reflowInputHolder->mFrame), 1002 "How'd we get a floated inline frame? " 1003 "The frame ctor should've dealt with this."); 1004 if (CanPlaceFrame(pfd, notSafeToBreak, continuingTextRun, 1005 savedOptionalBreakFrame != nullptr, reflowOutput, 1006 aReflowStatus, &optionalBreakAfterFits)) { 1007 if (!isEmpty) { 1008 psd->mHasNonemptyContent = true; 1009 mLineIsEmpty = false; 1010 if (!pfd->mSpan) { 1011 // nonempty leaf content has been placed 1012 mLineAtStart = false; 1013 } 1014 if (LayoutFrameType::Ruby == frameType) { 1015 mHasRuby = true; 1016 SyncAnnotationBounds(pfd); 1017 } 1018 } 1019 1020 // Place the frame, updating aBounds with the final size and 1021 // location. Then apply the bottom+right margins (as 1022 // appropriate) to the frame. 1023 PlaceFrame(pfd, reflowOutput); 1024 PerSpanData* span = pfd->mSpan; 1025 if (span) { 1026 // The frame we just finished reflowing is an inline 1027 // container. It needs its child frames aligned in the block direction, 1028 // so do most of it now. 1029 VerticalAlignFrames(span); 1030 } 1031 1032 if (!continuingTextRun && !psd->mNoWrap) { 1033 if (!LineIsEmpty() || placedFloat) { 1034 // record soft break opportunity after this content that can't be 1035 // part of a text run. This is not a text frame so we know 1036 // that offset INT32_MAX means "after the content". 1037 if ((!aFrame->IsPlaceholderFrame() || LineIsEmpty()) && 1038 NotifyOptionalBreakPosition(aFrame, INT32_MAX, 1039 optionalBreakAfterFits, 1040 gfxBreakPriority::eNormalBreak)) { 1041 // If this returns true then we are being told to actually break 1042 // here. 1043 aReflowStatus.SetInlineLineBreakAfter(); 1044 } 1045 } 1046 } 1047 } else { 1048 PushFrame(aFrame); 1049 aPushedFrame = true; 1050 // Undo any saved break positions that the frame might have told us about, 1051 // since we didn't end up placing it 1052 RestoreSavedBreakPosition(savedOptionalBreakFrame, 1053 savedOptionalBreakOffset, 1054 savedOptionalBreakPriority); 1055 } 1056 } else { 1057 PushFrame(aFrame); 1058 aPushedFrame = true; 1059 } 1060 1061 #ifdef REALLY_NOISY_REFLOW 1062 nsIFrame::IndentBy(stdout, mSpanDepth); 1063 printf("End ReflowFrame "); 1064 aFrame->ListTag(stdout); 1065 printf(" status=%x\n", aReflowStatus); 1066 #endif 1067 } 1068 1069 void nsLineLayout::AllowForStartMargin(PerFrameData* pfd, 1070 ReflowInput& aReflowInput) { 1071 NS_ASSERTION(!aReflowInput.mStyleDisplay->IsFloating(aReflowInput.mFrame), 1072 "How'd we get a floated inline frame? " 1073 "The frame ctor should've dealt with this."); 1074 1075 WritingMode lineWM = mRootSpan->mWritingMode; 1076 1077 // Only apply start-margin on the first-in flow for inline frames, 1078 // and make sure to not apply it to any inline other than the first 1079 // in an ib split. Note that the ib sibling (block-in-inline 1080 // sibling) annotations only live on the first continuation, but we 1081 // don't want to apply the start margin for later continuations 1082 // anyway. For box-decoration-break:clone we apply the start-margin 1083 // on all continuations. 1084 if ((pfd->mFrame->GetPrevContinuation() || 1085 pfd->mFrame->FrameIsNonFirstInIBSplit()) && 1086 aReflowInput.mStyleBorder->mBoxDecorationBreak == 1087 StyleBoxDecorationBreak::Slice) { 1088 // Zero this out so that when we compute the max-element-width of 1089 // the frame we will properly avoid adding in the starting margin. 1090 pfd->mMargin.IStart(lineWM) = 0; 1091 } else if (NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedISize()) { 1092 NS_WARNING_ASSERTION( 1093 NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableISize(), 1094 "have unconstrained inline-size; this should only result from very " 1095 "large sizes, not attempts at intrinsic inline-size calculation"); 1096 // For inline-ish and text-ish things (which don't compute widths 1097 // in the reflow input), adjust available inline-size to account 1098 // for the start margin. The end margin will be accounted for when 1099 // we finish flowing the frame. 1100 WritingMode wm = aReflowInput.GetWritingMode(); 1101 aReflowInput.SetAvailableISize( 1102 aReflowInput.AvailableISize() - 1103 pfd->mMargin.ConvertTo(wm, lineWM).IStart(wm)); 1104 } 1105 } 1106 1107 nscoord nsLineLayout::GetCurrentFrameInlineDistanceFromBlock() { 1108 nscoord x = 0; 1109 for (const auto* psd = mCurrentSpan; psd; psd = psd->mParent) { 1110 x += psd->mICoord; 1111 } 1112 return x; 1113 } 1114 1115 /** 1116 * This method syncs bounds of ruby annotations and ruby annotation 1117 * containers from their rect. It is necessary because: 1118 * Containers are not part of the line in their levels, which means 1119 * their bounds are not set properly before. 1120 * Ruby annotations' position may have been changed when reflowing 1121 * their containers. 1122 */ 1123 void nsLineLayout::SyncAnnotationBounds(PerFrameData* aRubyFrame) { 1124 MOZ_ASSERT(aRubyFrame->mFrame->IsRubyFrame()); 1125 MOZ_ASSERT(aRubyFrame->mSpan); 1126 1127 PerSpanData* span = aRubyFrame->mSpan; 1128 WritingMode lineWM = mRootSpan->mWritingMode; 1129 for (PerFrameData* pfd = span->mFirstFrame; pfd; pfd = pfd->mNext) { 1130 for (PerFrameData* rtc = pfd->mNextAnnotation; rtc; 1131 rtc = rtc->mNextAnnotation) { 1132 if (lineWM.IsOrthogonalTo(rtc->mFrame->GetWritingMode())) { 1133 // Inter-character case: don't attempt to sync annotation bounds. 1134 continue; 1135 } 1136 // When the annotation container is reflowed, the width of the 1137 // ruby container is unknown so we use a dummy container size; 1138 // in the case of RTL block direction, the final position will be 1139 // fixed up later. 1140 const nsSize dummyContainerSize; 1141 LogicalRect rtcBounds(lineWM, rtc->mFrame->GetRect(), dummyContainerSize); 1142 rtc->mBounds = rtcBounds; 1143 nsSize rtcSize = rtcBounds.Size(lineWM).GetPhysicalSize(lineWM); 1144 for (PerFrameData* rt = rtc->mSpan->mFirstFrame; rt; rt = rt->mNext) { 1145 LogicalRect rtBounds = rt->mFrame->GetLogicalRect(lineWM, rtcSize); 1146 MOZ_ASSERT(rt->mBounds.Size(lineWM) == rtBounds.Size(lineWM), 1147 "Size of the annotation should not have been changed"); 1148 rt->mBounds.SetOrigin(lineWM, rtBounds.Origin(lineWM)); 1149 } 1150 } 1151 } 1152 } 1153 1154 /** 1155 * See if the frame can be placed now that we know it's desired size. 1156 * We can always place the frame if the line is empty. Note that we 1157 * know that the reflow-status is not a break-before because if it was 1158 * ReflowFrame above would have returned false, preventing this method 1159 * from being called. The logic in this method assumes that. 1160 * 1161 * Note that there is no check against the Y coordinate because we 1162 * assume that the caller will take care of that. 1163 */ 1164 bool nsLineLayout::CanPlaceFrame(PerFrameData* pfd, bool aNotSafeToBreak, 1165 bool aFrameCanContinueTextRun, 1166 bool aCanRollBackBeforeFrame, 1167 ReflowOutput& aMetrics, 1168 nsReflowStatus& aStatus, 1169 bool* aOptionalBreakAfterFits) { 1170 MOZ_ASSERT(pfd && pfd->mFrame, "bad args, null pointers for frame data"); 1171 1172 *aOptionalBreakAfterFits = true; 1173 1174 WritingMode lineWM = mRootSpan->mWritingMode; 1175 /* 1176 * We want to only apply the end margin if we're the last continuation and 1177 * either not in an {ib} split or the last inline in it. In all other 1178 * cases we want to zero it out. That means zeroing it out if any of these 1179 * conditions hold: 1180 * 1) The frame is not complete (in this case it will get a next-in-flow) 1181 * 2) The frame is complete but has a non-fluid continuation on its 1182 * continuation chain. Note that if it has a fluid continuation, that 1183 * continuation will get destroyed later, so we don't want to drop the 1184 * end-margin in that case. 1185 * 3) The frame is in an {ib} split and is not the last part. 1186 * 1187 * However, none of that applies if this is a letter frame (XXXbz why?) 1188 * 1189 * For box-decoration-break:clone we apply the end margin on all 1190 * continuations (that are not letter frames). 1191 */ 1192 if ((aStatus.IsIncomplete() || 1193 pfd->mFrame->LastInFlow()->GetNextContinuation() || 1194 pfd->mFrame->FrameIsNonLastInIBSplit()) && 1195 !pfd->mIsLetterFrame && 1196 pfd->mFrame->StyleBorder()->mBoxDecorationBreak == 1197 StyleBoxDecorationBreak::Slice) { 1198 pfd->mMargin.IEnd(lineWM) = 0; 1199 } 1200 1201 // Apply the start margin to the frame bounds. 1202 nscoord startMargin = pfd->mMargin.IStart(lineWM); 1203 nscoord endMargin = pfd->mMargin.IEnd(lineWM); 1204 1205 pfd->mBounds.IStart(lineWM) += startMargin; 1206 1207 PerSpanData* psd = mCurrentSpan; 1208 if (psd->mNoWrap) { 1209 // When wrapping is off, everything fits. 1210 return true; 1211 } 1212 1213 #ifdef NOISY_CAN_PLACE_FRAME 1214 if (psd->mFrame) { 1215 psd->mFrame->mFrame->ListTag(stdout); 1216 } 1217 printf(": aNotSafeToBreak=%s frame=", aNotSafeToBreak ? "true" : "false"); 1218 pfd->mFrame->ListTag(stdout); 1219 printf(" frameWidth=%d, margins=%d,%d\n", 1220 pfd->mBounds.IEnd(lineWM) + endMargin - psd->mICoord, startMargin, 1221 endMargin); 1222 #endif 1223 1224 // Set outside to true if the result of the reflow leads to the 1225 // frame sticking outside of our available area. 1226 bool outside = 1227 pfd->mBounds.IEnd(lineWM) - mTrimmableISize + endMargin > psd->mIEnd; 1228 if (!outside) { 1229 // If it fits, it fits 1230 #ifdef NOISY_CAN_PLACE_FRAME 1231 printf(" ==> inside\n"); 1232 #endif 1233 return true; 1234 } 1235 *aOptionalBreakAfterFits = false; 1236 1237 // When it doesn't fit, check for a few special conditions where we 1238 // allow it to fit anyway. 1239 if (0 == startMargin + pfd->mBounds.ISize(lineWM) + endMargin) { 1240 // Empty frames always fit right where they are 1241 #ifdef NOISY_CAN_PLACE_FRAME 1242 printf(" ==> empty frame fits\n"); 1243 #endif 1244 return true; 1245 } 1246 1247 // another special case: always place a BR 1248 if (pfd->mFrame->IsBrFrame()) { 1249 #ifdef NOISY_CAN_PLACE_FRAME 1250 printf(" ==> BR frame fits\n"); 1251 #endif 1252 return true; 1253 } 1254 1255 if (aNotSafeToBreak) { 1256 // There are no frames on the line that take up width and the line is 1257 // not impacted by floats, so we must allow the current frame to be 1258 // placed on the line 1259 #ifdef NOISY_CAN_PLACE_FRAME 1260 printf(" ==> not-safe and not-impacted fits: "); 1261 while (nullptr != psd) { 1262 printf("<psd=%p x=%d left=%d> ", psd, psd->mICoord, psd->mIStart); 1263 psd = psd->mParent; 1264 } 1265 printf("\n"); 1266 #endif 1267 return true; 1268 } 1269 1270 // Special check for span frames 1271 if (pfd->mSpan && pfd->mSpan->mContainsFloat) { 1272 // If the span either directly or indirectly contains a float then 1273 // it fits. Why? It's kind of complicated, but here goes: 1274 // 1275 // 1. CanPlaceFrame is used for all frame placements on a line, 1276 // and in a span. This includes recursively placement of frames 1277 // inside of spans, and the span itself. Because the logic always 1278 // checks for room before proceeding (the code above here), the 1279 // only things on a line will be those things that "fit". 1280 // 1281 // 2. Before a float is placed on a line, the line has to be empty 1282 // (otherwise it's a "below current line" float and will be placed 1283 // after the line). 1284 // 1285 // Therefore, if the span directly or indirectly has a float 1286 // then it means that at the time of the placement of the float 1287 // the line was empty. Because of #1, only the frames that fit can 1288 // be added after that point, therefore we can assume that the 1289 // current span being placed has fit. 1290 // 1291 // So how do we get here and have a span that should already fit 1292 // and yet doesn't: Simple: span's that have the no-wrap attribute 1293 // set on them and contain a float and are placed where they 1294 // don't naturally fit. 1295 return true; 1296 } 1297 1298 if (aFrameCanContinueTextRun) { 1299 // Let it fit, but we reserve the right to roll back. 1300 // Note that we usually won't get here because a text frame will break 1301 // itself to avoid exceeding the available width. 1302 // We'll only get here for text frames that couldn't break early enough. 1303 #ifdef NOISY_CAN_PLACE_FRAME 1304 printf(" ==> placing overflowing textrun, requesting backup\n"); 1305 #endif 1306 1307 // We will want to try backup. 1308 mNeedBackup = true; 1309 return true; 1310 } 1311 1312 #ifdef NOISY_CAN_PLACE_FRAME 1313 printf(" ==> didn't fit\n"); 1314 #endif 1315 aStatus.SetInlineLineBreakBeforeAndReset(); 1316 return false; 1317 } 1318 1319 /** 1320 * Place the frame. Update running counters. 1321 */ 1322 void nsLineLayout::PlaceFrame(PerFrameData* pfd, ReflowOutput& aMetrics) { 1323 WritingMode lineWM = mRootSpan->mWritingMode; 1324 1325 // If the frame's block direction does not match the line's, we can't use 1326 // its ascent; instead, treat it as a block with baseline at the block-end 1327 // edge (or block-begin in the case of an "inverted" line). 1328 if (pfd->mWritingMode.GetBlockDir() != lineWM.GetBlockDir()) { 1329 pfd->mAscent = lineWM.IsAlphabeticalBaseline() 1330 ? lineWM.IsLineInverted() ? 0 : aMetrics.BSize(lineWM) 1331 : aMetrics.BSize(lineWM) / 2; 1332 } else { 1333 // For inline reflow participants, baseline may get assigned as the frame is 1334 // vertically aligned, which happens after this. 1335 const auto baselineSource = pfd->mFrame->StyleDisplay()->mBaselineSource; 1336 if (baselineSource == StyleBaselineSource::Auto || 1337 pfd->mFrame->IsLineParticipant()) { 1338 if (aMetrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) { 1339 pfd->mAscent = pfd->mFrame->GetLogicalBaseline(lineWM); 1340 } else { 1341 pfd->mAscent = aMetrics.BlockStartAscent(); 1342 } 1343 } else { 1344 const auto sourceGroup = [baselineSource]() { 1345 switch (baselineSource) { 1346 case StyleBaselineSource::First: 1347 return BaselineSharingGroup::First; 1348 case StyleBaselineSource::Last: 1349 return BaselineSharingGroup::Last; 1350 case StyleBaselineSource::Auto: 1351 break; 1352 } 1353 MOZ_ASSERT_UNREACHABLE("Auto should be already handled?"); 1354 return BaselineSharingGroup::First; 1355 }(); 1356 // We ignore line-layout specific layout quirks by setting 1357 // `BaselineExportContext::Other`. 1358 // Note(dshin): For a lot of frames, the export context does not make a 1359 // difference, and we may be wasting the value cached in 1360 // `BlockStartAscent`. 1361 pfd->mAscent = pfd->mFrame->GetLogicalBaseline( 1362 lineWM, sourceGroup, BaselineExportContext::Other); 1363 } 1364 } 1365 1366 // Advance to next inline coordinate 1367 mCurrentSpan->mICoord = pfd->mBounds.IEnd(lineWM) + pfd->mMargin.IEnd(lineWM); 1368 1369 // Count the number of non-placeholder frames on the line... 1370 if (pfd->mFrame->IsPlaceholderFrame()) { 1371 NS_ASSERTION( 1372 pfd->mBounds.ISize(lineWM) == 0 && pfd->mBounds.BSize(lineWM) == 0, 1373 "placeholders should have 0 width/height (checking " 1374 "placeholders were never counted by the old code in " 1375 "this function)"); 1376 } else { 1377 mTotalPlacedFrames++; 1378 } 1379 } 1380 1381 void nsLineLayout::AddMarkerFrame(nsIFrame* aFrame, 1382 const ReflowOutput& aMetrics) { 1383 MOZ_ASSERT(mCurrentSpan == mRootSpan, "bad linelayout user"); 1384 MOZ_ASSERT(mGotLineBox, "must have line box"); 1385 1386 nsBlockFrame* blockFrame = do_QueryFrame(LineContainerFrame()); 1387 MOZ_ASSERT(blockFrame, "must be for block"); 1388 if (!blockFrame->MarkerIsEmpty(aFrame)) { 1389 mLineIsEmpty = false; 1390 mHasMarker = true; 1391 mLineBox->SetHasMarker(); 1392 } 1393 1394 WritingMode lineWM = mRootSpan->mWritingMode; 1395 PerFrameData* pfd = NewPerFrameData(aFrame); 1396 PerSpanData* psd = mRootSpan; 1397 1398 MOZ_ASSERT(psd->mFirstFrame, "adding marker to an empty line?"); 1399 // Prepend the marker frame to the line. 1400 psd->mFirstFrame->mPrev = pfd; 1401 pfd->mNext = psd->mFirstFrame; 1402 psd->mFirstFrame = pfd; 1403 1404 pfd->mIsMarker = true; 1405 if (aMetrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) { 1406 pfd->mAscent = aFrame->GetLogicalBaseline(lineWM); 1407 } else { 1408 pfd->mAscent = aMetrics.BlockStartAscent(); 1409 } 1410 1411 // Note: block-coord value will be updated during block-direction alignment 1412 pfd->mBounds = LogicalRect(lineWM, aFrame->GetRect(), ContainerSize()); 1413 pfd->mOverflowAreas = aMetrics.mOverflowAreas; 1414 } 1415 1416 void nsLineLayout::RemoveMarkerFrame(nsIFrame* aFrame) { 1417 PerSpanData* psd = mCurrentSpan; 1418 MOZ_ASSERT(psd == mRootSpan, "::marker on non-root span?"); 1419 MOZ_ASSERT(psd->mFirstFrame->mFrame == aFrame, 1420 "::marker is not the first frame?"); 1421 PerFrameData* pfd = psd->mFirstFrame; 1422 MOZ_ASSERT(pfd != psd->mLastFrame, "::marker is the only frame?"); 1423 pfd->mNext->mPrev = nullptr; 1424 psd->mFirstFrame = pfd->mNext; 1425 FreeFrame(pfd); 1426 } 1427 1428 #ifdef DEBUG 1429 void nsLineLayout::DumpPerSpanData(PerSpanData* psd, int32_t aIndent) { 1430 nsIFrame::IndentBy(stdout, aIndent); 1431 printf("%p: left=%d x=%d right=%d\n", static_cast<void*>(psd), psd->mIStart, 1432 psd->mICoord, psd->mIEnd); 1433 PerFrameData* pfd = psd->mFirstFrame; 1434 while (nullptr != pfd) { 1435 nsIFrame::IndentBy(stdout, aIndent + 1); 1436 pfd->mFrame->ListTag(stdout); 1437 nsRect rect = 1438 pfd->mBounds.GetPhysicalRect(psd->mWritingMode, ContainerSize()); 1439 printf(" %d,%d,%d,%d\n", rect.x, rect.y, rect.width, rect.height); 1440 if (pfd->mSpan) { 1441 DumpPerSpanData(pfd->mSpan, aIndent + 1); 1442 } 1443 pfd = pfd->mNext; 1444 } 1445 } 1446 #endif 1447 1448 void nsLineLayout::RecordNoWrapFloat(nsIFrame* aFloat) { 1449 GetOutermostLineLayout()->mBlockRS->mNoWrapFloats.AppendElement(aFloat); 1450 } 1451 1452 void nsLineLayout::FlushNoWrapFloats() { 1453 auto& noWrapFloats = GetOutermostLineLayout()->mBlockRS->mNoWrapFloats; 1454 for (nsIFrame* floatedFrame : noWrapFloats) { 1455 TryToPlaceFloat(floatedFrame); 1456 } 1457 noWrapFloats.Clear(); 1458 } 1459 1460 bool nsLineLayout::TryToPlaceFloat(nsIFrame* aFloat) { 1461 // Add mTrimmableISize to the available width since if the line ends here, the 1462 // width of the inline content will be reduced by mTrimmableISize. 1463 nscoord availableISize = 1464 mCurrentSpan->mIEnd - (mCurrentSpan->mICoord - mTrimmableISize); 1465 NS_ASSERTION(!(aFloat->IsLetterFrame() && GetFirstLetterStyleOK()), 1466 "FirstLetterStyle set on line with floating first letter"); 1467 return GetOutermostLineLayout()->AddFloat(aFloat, availableISize); 1468 } 1469 1470 bool nsLineLayout::NotifyOptionalBreakPosition(nsIFrame* aFrame, 1471 int32_t aOffset, bool aFits, 1472 gfxBreakPriority aPriority) { 1473 NS_ASSERTION(!aFits || !mNeedBackup, 1474 "Shouldn't be updating the break position with a break that fits" 1475 " after we've already flagged an overrun"); 1476 MOZ_ASSERT(mCurrentSpan, "Should be doing line layout"); 1477 if (mCurrentSpan->mNoWrap) { 1478 FlushNoWrapFloats(); 1479 } 1480 1481 // Remember the last break position that fits; if there was no break that fit, 1482 // just remember the first break 1483 if ((aFits && aPriority >= mLastOptionalBreakPriority) || 1484 !mLastOptionalBreakFrame) { 1485 mLastOptionalBreakFrame = aFrame; 1486 mLastOptionalBreakFrameOffset = aOffset; 1487 mLastOptionalBreakPriority = aPriority; 1488 } 1489 return aFrame && mForceBreakFrame == aFrame && 1490 mForceBreakFrameOffset == aOffset; 1491 } 1492 1493 #define VALIGN_OTHER 0 1494 #define VALIGN_TOP 1 1495 #define VALIGN_BOTTOM 2 1496 1497 void nsLineLayout::SetSpanForEmptyLine(PerSpanData* aPerSpanData, 1498 WritingMode aWM, 1499 const nsSize& aContainerSize, 1500 nscoord aBStartEdge) { 1501 for (PerFrameData* pfd = aPerSpanData->mFirstFrame; pfd; pfd = pfd->mNext) { 1502 // Ideally, if the frame would collapse itself - but it depends on 1503 // knowing that the line is empty. 1504 if (!pfd->mFrame->IsInlineFrame() && !pfd->mFrame->IsRubyFrame() && 1505 !pfd->mFrame->IsPlaceholderFrame()) { 1506 continue; 1507 } 1508 // Collapse the physical size to 0. 1509 pfd->mBounds.BStart(aWM) = aBStartEdge; 1510 pfd->mBounds.BSize(aWM) = 0; 1511 // Initialize mBlockDirAlign (though it doesn't make much difference 1512 // because we don't align empty boxes). 1513 pfd->mBlockDirAlign = VALIGN_OTHER; 1514 pfd->mFrame->SetRect(aWM, pfd->mBounds, aContainerSize); 1515 if (pfd->mSpan) { 1516 // For child spans, the block-start edge is relative to that of parent, 1517 // which is zero (since it is empty). See NOTE in 1518 // `nsLineLayout::ReflowFrame`. 1519 SetSpanForEmptyLine(pfd->mSpan, aWM, aContainerSize, 0); 1520 } 1521 } 1522 } 1523 1524 void nsLineLayout::VerticalAlignLine() { 1525 // Partially place the children of the block frame. The baseline for 1526 // this operation is set to zero so that the y coordinates for all 1527 // of the placed children will be relative to there. 1528 PerSpanData* psd = mRootSpan; 1529 if (mLineIsEmpty) { 1530 // This line is empty, and should be consisting of only inline elements. 1531 // (inline-block elements would make the line non-empty). 1532 SetSpanForEmptyLine(psd, mRootSpan->mWritingMode, ContainerSize(), 1533 mBStartEdge); 1534 1535 mFinalLineBSize = 0; 1536 if (mGotLineBox) { 1537 mLineBox->SetBounds(psd->mWritingMode, psd->mIStart, mBStartEdge, 1538 psd->mICoord - psd->mIStart, 0, ContainerSize()); 1539 1540 mLineBox->SetLogicalAscent(0); 1541 } 1542 return; 1543 } 1544 VerticalAlignFrames(psd); 1545 1546 // *** Note that comments here still use the anachronistic term 1547 // "line-height" when we really mean "size of the line in the block 1548 // direction", "vertical-align" when we really mean "alignment in 1549 // the block direction", and "top" and "bottom" when we really mean 1550 // "block start" and "block end". This is partly for brevity and 1551 // partly to retain the association with the CSS line-height and 1552 // vertical-align properties. 1553 // 1554 // Compute the line-height. The line-height will be the larger of: 1555 // 1556 // [1] maxBCoord - minBCoord (the distance between the first child's 1557 // block-start edge and the last child's block-end edge) 1558 // 1559 // [2] the maximum logical box block size (since not every frame may have 1560 // participated in #1; for example: "top" and "botttom" aligned frames) 1561 // 1562 // [3] the minimum line height ("line-height" property set on the 1563 // block frame) 1564 nscoord lineBSize = psd->mMaxBCoord - psd->mMinBCoord; 1565 1566 // Now that the line-height is computed, we need to know where the 1567 // baseline is in the line. Position baseline so that mMinBCoord is just 1568 // inside the start of the line box. 1569 nscoord baselineBCoord = mBStartEdge - std::min(0, psd->mMinBCoord); 1570 1571 // It's also possible that the line block-size isn't tall enough because 1572 // of "top" and "bottom" aligned elements that were not accounted for in 1573 // min/max BCoord. 1574 // 1575 // The CSS2 spec doesn't really say what happens when to the 1576 // baseline in this situations. What we do is if the largest start 1577 // aligned box block size is greater than the line block-size then we leave 1578 // the baseline alone. If the largest end aligned box is greater 1579 // than the line block-size then we slide the baseline forward by the extra 1580 // amount. 1581 // 1582 // Navigator 4 gives precedence to the first top/bottom aligned 1583 // object. We just let block end aligned objects win. 1584 if (lineBSize < mMaxEndBoxBSize) { 1585 // When the line is shorter than the maximum block start aligned box 1586 nscoord extra = mMaxEndBoxBSize - lineBSize; 1587 baselineBCoord += extra; 1588 lineBSize = mMaxEndBoxBSize; 1589 } 1590 lineBSize = std::max(lineBSize, mMaxStartBoxBSize); 1591 #ifdef NOISY_BLOCKDIR_ALIGN 1592 printf(" [line]==> lineBSize=%d baselineBCoord=%d\n", lineBSize, 1593 baselineBCoord); 1594 #endif 1595 1596 // Now position all of the frames in the root span. We will also 1597 // recurse over the child spans and place any frames we find with 1598 // vertical-align: top or bottom. 1599 // XXX PERFORMANCE: set a bit per-span to avoid the extra work 1600 // (propagate it upward too) 1601 WritingMode lineWM = psd->mWritingMode; 1602 for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) { 1603 if (pfd->mBlockDirAlign == VALIGN_OTHER) { 1604 pfd->mBounds.BStart(lineWM) += baselineBCoord; 1605 pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSize()); 1606 } 1607 } 1608 PlaceTopBottomFrames(psd, -mBStartEdge, lineBSize); 1609 1610 mFinalLineBSize = lineBSize; 1611 if (mGotLineBox) { 1612 // Fill in returned line-box and max-element-width data 1613 mLineBox->SetBounds(lineWM, psd->mIStart, mBStartEdge, 1614 psd->mICoord - psd->mIStart, lineBSize, 1615 ContainerSize()); 1616 1617 mLineBox->SetLogicalAscent(baselineBCoord - mBStartEdge); 1618 #ifdef NOISY_BLOCKDIR_ALIGN 1619 printf(" [line]==> bounds{x,y,w,h}={%d,%d,%d,%d} lh=%d a=%d\n", 1620 mLineBox->GetBounds().IStart(lineWM), 1621 mLineBox->GetBounds().BStart(lineWM), 1622 mLineBox->GetBounds().ISize(lineWM), 1623 mLineBox->GetBounds().BSize(lineWM), mFinalLineBSize, 1624 mLineBox->GetLogicalAscent()); 1625 #endif 1626 } 1627 } 1628 1629 // Place frames with CSS property vertical-align: top or bottom. 1630 void nsLineLayout::PlaceTopBottomFrames(PerSpanData* psd, 1631 nscoord aDistanceFromStart, 1632 nscoord aLineBSize) { 1633 for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) { 1634 PerSpanData* span = pfd->mSpan; 1635 #ifdef DEBUG 1636 NS_ASSERTION(0xFF != pfd->mBlockDirAlign, "umr"); 1637 #endif 1638 WritingMode lineWM = mRootSpan->mWritingMode; 1639 nsSize containerSize = ContainerSizeForSpan(psd); 1640 switch (pfd->mBlockDirAlign) { 1641 case VALIGN_TOP: 1642 if (span) { 1643 pfd->mBounds.BStart(lineWM) = -aDistanceFromStart - span->mMinBCoord; 1644 } else { 1645 pfd->mBounds.BStart(lineWM) = 1646 -aDistanceFromStart + pfd->mMargin.BStart(lineWM); 1647 } 1648 pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerSize); 1649 #ifdef NOISY_BLOCKDIR_ALIGN 1650 printf(" "); 1651 pfd->mFrame->ListTag(stdout); 1652 printf(": y=%d dTop=%d [bp.top=%d topLeading=%d]\n", 1653 pfd->mBounds.BStart(lineWM), aDistanceFromStart, 1654 span ? pfd->mBorderPadding.BStart(lineWM) : 0, 1655 span ? span->mBStartLeading : 0); 1656 #endif 1657 break; 1658 case VALIGN_BOTTOM: 1659 if (span) { 1660 // Compute bottom leading 1661 pfd->mBounds.BStart(lineWM) = 1662 -aDistanceFromStart + aLineBSize - span->mMaxBCoord; 1663 } else { 1664 pfd->mBounds.BStart(lineWM) = -aDistanceFromStart + aLineBSize - 1665 pfd->mMargin.BEnd(lineWM) - 1666 pfd->mBounds.BSize(lineWM); 1667 } 1668 pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerSize); 1669 #ifdef NOISY_BLOCKDIR_ALIGN 1670 printf(" "); 1671 pfd->mFrame->ListTag(stdout); 1672 printf(": y=%d\n", pfd->mBounds.BStart(lineWM)); 1673 #endif 1674 break; 1675 } 1676 if (span) { 1677 nscoord fromStart = aDistanceFromStart + pfd->mBounds.BStart(lineWM); 1678 PlaceTopBottomFrames(span, fromStart, aLineBSize); 1679 } 1680 } 1681 } 1682 1683 static nscoord GetBSizeOfEmphasisMarks(nsIFrame* aSpanFrame, float aInflation) { 1684 RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsOfEmphasisMarks( 1685 aSpanFrame->Style(), aSpanFrame->PresContext(), aInflation); 1686 return aSpanFrame->PresContext()->NormalizeRubyMetrics() 1687 ? (fm->TrimmedAscent() + fm->TrimmedDescent()) * 1688 aSpanFrame->PresContext()->RubyPositioningFactor() 1689 : fm->MaxHeight(); 1690 } 1691 1692 void nsLineLayout::AdjustLeadings(nsIFrame* spanFrame, PerSpanData* psd, 1693 const nsStyleText* aStyleText, 1694 float aInflation, 1695 bool* aZeroEffectiveSpanBox) { 1696 MOZ_ASSERT(spanFrame == psd->mFrame->mFrame); 1697 nscoord requiredStartLeading = 0; 1698 nscoord requiredEndLeading = 0; 1699 if (spanFrame->IsRubyFrame()) { 1700 // We may need to extend leadings here for ruby annotations as 1701 // required by section Line Spacing in the CSS Ruby spec. 1702 // See http://dev.w3.org/csswg/css-ruby/#line-height 1703 auto rubyFrame = static_cast<nsRubyFrame*>(spanFrame); 1704 RubyBlockLeadings leadings = rubyFrame->GetBlockLeadings(); 1705 requiredStartLeading += leadings.mStart; 1706 requiredEndLeading += leadings.mEnd; 1707 } 1708 if (aStyleText->HasEffectiveTextEmphasis()) { 1709 nscoord bsize = GetBSizeOfEmphasisMarks(spanFrame, aInflation); 1710 LogicalSide side = aStyleText->TextEmphasisSide( 1711 mRootSpan->mWritingMode, spanFrame->StyleFont()->mLanguage); 1712 if (spanFrame->PresContext()->NormalizeRubyMetrics()) { 1713 // Add extra leading for emphasis marks only if their bsize exceeds the 1714 // space built in to the font (difference between its max ascent/descent 1715 // and the em-normalized metrics that are used to position the mark). 1716 RefPtr fm = nsLayoutUtils::GetInflatedFontMetricsForFrame(spanFrame); 1717 float factor = spanFrame->PresContext()->RubyPositioningFactor(); 1718 if (side == LogicalSide::BStart) { 1719 requiredStartLeading += std::max( 1720 0, bsize - (fm->MaxAscent() - 1721 nscoord(NS_round(factor * fm->TrimmedAscent())))); 1722 } else { 1723 requiredEndLeading += std::max( 1724 0, bsize - (fm->MaxDescent() - 1725 nscoord(NS_round(factor * fm->TrimmedDescent())))); 1726 } 1727 } else { 1728 if (side == LogicalSide::BStart) { 1729 requiredStartLeading += bsize; 1730 } else { 1731 MOZ_ASSERT(side == LogicalSide::BEnd, 1732 "emphasis marks must be in block axis"); 1733 requiredEndLeading += bsize; 1734 } 1735 } 1736 } 1737 1738 nscoord requiredLeading = requiredStartLeading + requiredEndLeading; 1739 // If we do not require any additional leadings, don't touch anything 1740 // here even if it is greater than the original leading, because the 1741 // latter could be negative. 1742 if (requiredLeading != 0) { 1743 nscoord leading = psd->mBStartLeading + psd->mBEndLeading; 1744 nscoord deltaLeading = requiredLeading - leading; 1745 if (deltaLeading > 0) { 1746 // If the total leading is not wide enough for ruby annotations 1747 // and/or emphasis marks, extend the side which is not enough. If 1748 // both sides are not wide enough, replace the leadings with the 1749 // requested values. 1750 if (requiredStartLeading < psd->mBStartLeading) { 1751 psd->mBEndLeading += deltaLeading; 1752 } else if (requiredEndLeading < psd->mBEndLeading) { 1753 psd->mBStartLeading += deltaLeading; 1754 } else { 1755 psd->mBStartLeading = requiredStartLeading; 1756 psd->mBEndLeading = requiredEndLeading; 1757 } 1758 psd->mLogicalBSize += deltaLeading; 1759 // We have adjusted the leadings, it is no longer a zero 1760 // effective span box. 1761 *aZeroEffectiveSpanBox = false; 1762 } 1763 } 1764 } 1765 1766 static float GetInflationForBlockDirAlignment(nsIFrame* aFrame, 1767 nscoord aInflationMinFontSize) { 1768 if (aFrame->IsInSVGTextSubtree()) { 1769 const nsIFrame* container = 1770 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText); 1771 NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame"); 1772 return static_cast<const SVGTextFrame*>(container) 1773 ->GetFontSizeScaleFactor(); 1774 } 1775 return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize); 1776 } 1777 1778 bool nsLineLayout::ShouldApplyLineHeightInPreserveWhiteSpace( 1779 const PerSpanData* psd) { 1780 if (psd->mFrame->mFrame->Style()->IsAnonBox()) { 1781 // e.g. An empty `input[type=button]` should still be line-height sized. 1782 return true; 1783 } 1784 1785 for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) { 1786 if (!pfd->mIsEmpty) { 1787 return true; 1788 } 1789 } 1790 return false; 1791 } 1792 1793 #define BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM nscoord_MAX 1794 #define BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM nscoord_MIN 1795 1796 // Place frames in the block direction within a given span (CSS property 1797 // vertical-align) Note: this doesn't place frames with vertical-align: 1798 // top or bottom as those have to wait until the entire line box block 1799 // size is known. This is called after the span frame has finished being 1800 // reflowed so that we know its block size. 1801 void nsLineLayout::VerticalAlignFrames(PerSpanData* psd) { 1802 // Get parent frame info 1803 PerFrameData* spanFramePFD = psd->mFrame; 1804 nsIFrame* spanFrame = spanFramePFD->mFrame; 1805 1806 // Get the parent frame's font for all of the frames in this span 1807 float inflation = 1808 GetInflationForBlockDirAlignment(spanFrame, mInflationMinFontSize); 1809 RefPtr<nsFontMetrics> fm = 1810 nsLayoutUtils::GetFontMetricsForFrame(spanFrame, inflation); 1811 1812 bool preMode = mStyleText->WhiteSpaceIsSignificant(); 1813 1814 // See if the span is an empty continuation. It's an empty continuation iff: 1815 // - it has a prev-in-flow 1816 // - it has no next in flow 1817 // - it's zero sized 1818 WritingMode lineWM = mRootSpan->mWritingMode; 1819 bool emptyContinuation = psd != mRootSpan && spanFrame->GetPrevInFlow() && 1820 !spanFrame->GetNextInFlow() && 1821 spanFramePFD->mBounds.IsZeroSize(); 1822 1823 #ifdef NOISY_BLOCKDIR_ALIGN 1824 printf("[%sSpan]", (psd == mRootSpan) ? "Root" : ""); 1825 spanFrame->ListTag(stdout); 1826 printf(": preMode=%s strictMode=%s w/h=%d,%d emptyContinuation=%s", 1827 preMode ? "yes" : "no", 1828 mPresContext->CompatibilityMode() != eCompatibility_NavQuirks ? "yes" 1829 : "no", 1830 spanFramePFD->mBounds.ISize(lineWM), 1831 spanFramePFD->mBounds.BSize(lineWM), emptyContinuation ? "yes" : "no"); 1832 if (psd != mRootSpan) { 1833 printf(" bp=%d,%d,%d,%d margin=%d,%d,%d,%d", 1834 spanFramePFD->mBorderPadding.Top(lineWM), 1835 spanFramePFD->mBorderPadding.Right(lineWM), 1836 spanFramePFD->mBorderPadding.Bottom(lineWM), 1837 spanFramePFD->mBorderPadding.Left(lineWM), 1838 spanFramePFD->mMargin.Top(lineWM), 1839 spanFramePFD->mMargin.Right(lineWM), 1840 spanFramePFD->mMargin.Bottom(lineWM), 1841 spanFramePFD->mMargin.Left(lineWM)); 1842 } 1843 printf("\n"); 1844 #endif 1845 1846 // Compute the span's zeroEffectiveSpanBox flag. What we are trying 1847 // to determine is how we should treat the span: should it act 1848 // "normally" according to css2 or should it effectively 1849 // "disappear". 1850 // 1851 // In general, if the document being processed is in full standards 1852 // mode then it should act normally (with one exception). The 1853 // exception case is when a span is continued and yet the span is 1854 // empty (e.g. compressed whitespace). For this kind of span we treat 1855 // it as if it were not there so that it doesn't impact the 1856 // line block-size. 1857 // 1858 // In almost standards mode or quirks mode, we should sometimes make 1859 // it disappear. The cases that matter are those where the span 1860 // contains no real text elements that would provide an ascent and 1861 // descent and height. However, if css style elements have been 1862 // applied to the span (border/padding/margin) so that it's clear the 1863 // document author is intending css2 behavior then we act as if strict 1864 // mode is set. 1865 // 1866 // This code works correctly for preMode, because a blank line 1867 // in PRE mode is encoded as a text node with a LF in it, since 1868 // text nodes with only whitespace are considered in preMode. 1869 // 1870 // Much of this logic is shared with the various implementations of 1871 // nsIFrame::IsEmpty since they need to duplicate the way it makes 1872 // some lines empty. However, nsIFrame::IsEmpty can't be reused here 1873 // since this code sets zeroEffectiveSpanBox even when there are 1874 // non-empty children. 1875 bool zeroEffectiveSpanBox = false; 1876 // XXXldb If we really have empty continuations, then all these other 1877 // checks don't make sense for them. 1878 // XXXldb This should probably just use nsIFrame::IsSelfEmpty, assuming that 1879 // it agrees with this code. (If it doesn't agree, it probably should.) 1880 if ((emptyContinuation || 1881 mPresContext->CompatibilityMode() != eCompatibility_FullStandards) && 1882 ((psd == mRootSpan) || (spanFramePFD->mBorderPadding.IsAllZero() && 1883 spanFramePFD->mMargin.IsAllZero()))) { 1884 // This code handles an issue with compatibility with non-css 1885 // conformant browsers. In particular, there are some cases 1886 // where the font-size and line-height for a span must be 1887 // ignored and instead the span must *act* as if it were zero 1888 // sized. In general, if the span contains any non-compressed 1889 // text then we don't use this logic. 1890 // However, this is not propagated outwards, since (in compatibility 1891 // mode) we don't want big line heights for things like 1892 // <p><font size="-1">Text</font></p> 1893 1894 // We shouldn't include any whitespace that collapses, unless we're 1895 // preformatted (in which case it shouldn't, but the width=0 test is 1896 // perhaps incorrect). This includes whitespace at the beginning of 1897 // a line and whitespace preceded (?) by other whitespace. 1898 // See bug 134580 and bug 155333. 1899 zeroEffectiveSpanBox = true; 1900 for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) { 1901 if (pfd->mIsTextFrame && 1902 (pfd->mIsNonWhitespaceTextFrame || preMode || 1903 pfd->mBounds.ISize(mRootSpan->mWritingMode) != 0)) { 1904 zeroEffectiveSpanBox = false; 1905 break; 1906 } 1907 } 1908 } 1909 1910 // Setup baselineBCoord, minBCoord, and maxBCoord 1911 nscoord baselineBCoord, minBCoord, maxBCoord; 1912 if (psd == mRootSpan) { 1913 // Use a zero baselineBCoord since we don't yet know where the baseline 1914 // will be (until we know how tall the line is; then we will 1915 // know). In addition, use extreme values for the minBCoord and maxBCoord 1916 // values so that only the child frames will impact their values 1917 // (since these are children of the block, there is no span box to 1918 // provide initial values). 1919 baselineBCoord = 0; 1920 minBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM; 1921 maxBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM; 1922 #ifdef NOISY_BLOCKDIR_ALIGN 1923 printf("[RootSpan]"); 1924 spanFrame->ListTag(stdout); 1925 printf( 1926 ": pass1 valign frames: topEdge=%d minLineBSize=%d " 1927 "zeroEffectiveSpanBox=%s\n", 1928 mBStartEdge, mMinLineBSize, zeroEffectiveSpanBox ? "yes" : "no"); 1929 #endif 1930 } else { 1931 // Compute the logical block size for this span. The logical block size 1932 // is based on the "line-height" value, not the font-size. Also 1933 // compute the top leading. 1934 float inflation = 1935 GetInflationForBlockDirAlignment(spanFrame, mInflationMinFontSize); 1936 nscoord logicalBSize = ReflowInput::CalcLineHeight( 1937 *spanFrame->Style(), spanFrame->PresContext(), spanFrame->GetContent(), 1938 mLineContainerRI.ComputedHeight(), inflation); 1939 nscoord contentBSize = spanFramePFD->mBounds.BSize(lineWM) - 1940 spanFramePFD->mBorderPadding.BStartEnd(lineWM); 1941 1942 // Special-case for a ::first-letter frame, set the line height to 1943 // the frame block size if the user has left line-height == normal 1944 if (spanFramePFD->mIsLetterFrame && !spanFrame->GetPrevInFlow() && 1945 spanFrame->StyleFont()->mLineHeight.IsNormal()) { 1946 logicalBSize = spanFramePFD->mBounds.BSize(lineWM); 1947 } 1948 1949 nscoord leading = logicalBSize - contentBSize; 1950 psd->mBStartLeading = leading / 2; 1951 psd->mBEndLeading = leading - psd->mBStartLeading; 1952 psd->mLogicalBSize = logicalBSize; 1953 AdjustLeadings(spanFrame, psd, spanFrame->StyleText(), inflation, 1954 &zeroEffectiveSpanBox); 1955 1956 if (zeroEffectiveSpanBox) { 1957 // When the span-box is to be ignored, zero out the initial 1958 // values so that the span doesn't impact the final line 1959 // height. The contents of the span can impact the final line 1960 // height. 1961 1962 // Note that things are readjusted for this span after its children 1963 // are reflowed 1964 minBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM; 1965 maxBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM; 1966 } else { 1967 // The initial values for the min and max block coord values are in the 1968 // span's coordinate space, and cover the logical block size of the span. 1969 // If there are child frames in this span that stick out of this area 1970 // then the minBCoord and maxBCoord are updated by the amount of logical 1971 // blockSize that is outside this range. 1972 minBCoord = 1973 spanFramePFD->mBorderPadding.BStart(lineWM) - psd->mBStartLeading; 1974 maxBCoord = minBCoord + psd->mLogicalBSize; 1975 } 1976 1977 // This is the distance from the top edge of the parents visual 1978 // box to the baseline. The span already computed this for us, 1979 // so just use it. 1980 *psd->mBaseline = baselineBCoord = spanFramePFD->mAscent; 1981 1982 #ifdef NOISY_BLOCKDIR_ALIGN 1983 printf("[%sSpan]", (psd == mRootSpan) ? "Root" : ""); 1984 spanFrame->ListTag(stdout); 1985 printf( 1986 ": baseLine=%d logicalBSize=%d topLeading=%d h=%d bp=%d,%d " 1987 "zeroEffectiveSpanBox=%s\n", 1988 baselineBCoord, psd->mLogicalBSize, psd->mBStartLeading, 1989 spanFramePFD->mBounds.BSize(lineWM), 1990 spanFramePFD->mBorderPadding.Top(lineWM), 1991 spanFramePFD->mBorderPadding.Bottom(lineWM), 1992 zeroEffectiveSpanBox ? "yes" : "no"); 1993 #endif 1994 } 1995 1996 nscoord maxStartBoxBSize = 0; 1997 nscoord maxEndBoxBSize = 0; 1998 PerFrameData* pfd = psd->mFirstFrame; 1999 while (nullptr != pfd) { 2000 nsIFrame* frame = pfd->mFrame; 2001 2002 // sanity check (see bug 105168, non-reproducible crashes from null frame) 2003 NS_ASSERTION(frame, 2004 "null frame in PerFrameData - something is very very bad"); 2005 if (!frame) { 2006 return; 2007 } 2008 2009 // Compute the logical block size of the frame 2010 nscoord logicalBSize; 2011 PerSpanData* frameSpan = pfd->mSpan; 2012 if (frameSpan) { 2013 // For span frames the logical-block-size and start-leading were 2014 // pre-computed when the span was reflowed. 2015 logicalBSize = frameSpan->mLogicalBSize; 2016 } else { 2017 // For other elements the logical block size is the same as the 2018 // frame's block size plus its margins. 2019 logicalBSize = 2020 pfd->mBounds.BSize(lineWM) + pfd->mMargin.BStartEnd(lineWM); 2021 if (logicalBSize < 0 && 2022 mPresContext->CompatibilityMode() != eCompatibility_FullStandards) { 2023 pfd->mAscent -= logicalBSize; 2024 logicalBSize = 0; 2025 } 2026 } 2027 2028 // Get vertical-align property ("vertical-align" is the CSS name for 2029 // block-direction align) 2030 const auto& verticalAlign = frame->StyleDisplay()->mVerticalAlign; 2031 Maybe<StyleVerticalAlignKeyword> verticalAlignEnum = 2032 frame->VerticalAlignEnum(); 2033 #ifdef NOISY_BLOCKDIR_ALIGN 2034 printf(" [frame]"); 2035 frame->ListTag(stdout); 2036 printf(": verticalAlignIsKw=%d (enum == %d", verticalAlign.IsKeyword(), 2037 verticalAlign.IsKeyword() 2038 ? static_cast<int>(verticalAlign.AsKeyword()) 2039 : -1); 2040 if (verticalAlignEnum) { 2041 printf(", after SVG dominant-baseline conversion == %d", 2042 static_cast<int>(*verticalAlignEnum)); 2043 } 2044 printf(")\n"); 2045 #endif 2046 2047 if (verticalAlignEnum) { 2048 StyleVerticalAlignKeyword keyword = *verticalAlignEnum; 2049 if (lineWM.IsVertical()) { 2050 if (keyword == StyleVerticalAlignKeyword::Middle) { 2051 // For vertical writing mode where the dominant baseline is centered 2052 // (i.e. text-orientation is not sideways-*), we remap 'middle' to 2053 // 'middle-with-baseline' so that images align sensibly with the 2054 // center-baseline-aligned text. 2055 if (!lineWM.IsSideways()) { 2056 keyword = StyleVerticalAlignKeyword::MozMiddleWithBaseline; 2057 } 2058 } else if (lineWM.IsLineInverted()) { 2059 // Swap the meanings of top and bottom when line is inverted 2060 // relative to block direction. 2061 switch (keyword) { 2062 case StyleVerticalAlignKeyword::Top: 2063 keyword = StyleVerticalAlignKeyword::Bottom; 2064 break; 2065 case StyleVerticalAlignKeyword::Bottom: 2066 keyword = StyleVerticalAlignKeyword::Top; 2067 break; 2068 case StyleVerticalAlignKeyword::TextTop: 2069 keyword = StyleVerticalAlignKeyword::TextBottom; 2070 break; 2071 case StyleVerticalAlignKeyword::TextBottom: 2072 keyword = StyleVerticalAlignKeyword::TextTop; 2073 break; 2074 default: 2075 break; 2076 } 2077 } 2078 } 2079 2080 // baseline coord that may be adjusted for script offset 2081 nscoord revisedBaselineBCoord = baselineBCoord; 2082 2083 // For superscript and subscript, raise or lower the baseline of the box 2084 // to the proper offset of the parent's box, then proceed as for BASELINE 2085 if (keyword == StyleVerticalAlignKeyword::Sub || 2086 keyword == StyleVerticalAlignKeyword::Super) { 2087 revisedBaselineBCoord += lineWM.FlowRelativeToLineRelativeFactor() * 2088 (keyword == StyleVerticalAlignKeyword::Sub 2089 ? fm->SubscriptOffset() 2090 : -fm->SuperscriptOffset()); 2091 keyword = StyleVerticalAlignKeyword::Baseline; 2092 } 2093 2094 switch (keyword) { 2095 default: 2096 case StyleVerticalAlignKeyword::Baseline: 2097 pfd->mBounds.BStart(lineWM) = revisedBaselineBCoord - pfd->mAscent; 2098 pfd->mBlockDirAlign = VALIGN_OTHER; 2099 break; 2100 2101 case StyleVerticalAlignKeyword::Top: { 2102 pfd->mBlockDirAlign = VALIGN_TOP; 2103 nscoord subtreeBSize = logicalBSize; 2104 if (frameSpan) { 2105 subtreeBSize = frameSpan->mMaxBCoord - frameSpan->mMinBCoord; 2106 NS_ASSERTION(subtreeBSize >= logicalBSize, 2107 "unexpected subtree block size"); 2108 } 2109 if (subtreeBSize > maxStartBoxBSize) { 2110 maxStartBoxBSize = subtreeBSize; 2111 } 2112 break; 2113 } 2114 2115 case StyleVerticalAlignKeyword::Bottom: { 2116 pfd->mBlockDirAlign = VALIGN_BOTTOM; 2117 nscoord subtreeBSize = logicalBSize; 2118 if (frameSpan) { 2119 subtreeBSize = frameSpan->mMaxBCoord - frameSpan->mMinBCoord; 2120 NS_ASSERTION(subtreeBSize >= logicalBSize, 2121 "unexpected subtree block size"); 2122 } 2123 if (subtreeBSize > maxEndBoxBSize) { 2124 maxEndBoxBSize = subtreeBSize; 2125 } 2126 break; 2127 } 2128 2129 case StyleVerticalAlignKeyword::Middle: { 2130 // Align the midpoint of the frame with 1/2 the parents 2131 // x-height above the baseline. 2132 nscoord parentXHeight = 2133 lineWM.FlowRelativeToLineRelativeFactor() * fm->XHeight(); 2134 if (frameSpan) { 2135 pfd->mBounds.BStart(lineWM) = 2136 baselineBCoord - 2137 (parentXHeight + pfd->mBounds.BSize(lineWM)) / 2; 2138 } else { 2139 pfd->mBounds.BStart(lineWM) = baselineBCoord - 2140 (parentXHeight + logicalBSize) / 2 + 2141 pfd->mMargin.BStart(lineWM); 2142 } 2143 pfd->mBlockDirAlign = VALIGN_OTHER; 2144 break; 2145 } 2146 2147 case StyleVerticalAlignKeyword::TextTop: { 2148 // The top of the logical box is aligned with the top of 2149 // the parent element's text. 2150 // XXX For vertical text we will need a new API to get the logical 2151 // max-ascent here 2152 nscoord parentAscent = 2153 lineWM.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent(); 2154 if (frameSpan) { 2155 pfd->mBounds.BStart(lineWM) = baselineBCoord - parentAscent - 2156 pfd->mBorderPadding.BStart(lineWM) + 2157 frameSpan->mBStartLeading; 2158 } else { 2159 pfd->mBounds.BStart(lineWM) = 2160 baselineBCoord - parentAscent + pfd->mMargin.BStart(lineWM); 2161 } 2162 pfd->mBlockDirAlign = VALIGN_OTHER; 2163 break; 2164 } 2165 2166 case StyleVerticalAlignKeyword::TextBottom: { 2167 // The bottom of the logical box is aligned with the 2168 // bottom of the parent elements text. 2169 nscoord parentDescent = 2170 lineWM.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent(); 2171 if (frameSpan) { 2172 pfd->mBounds.BStart(lineWM) = 2173 baselineBCoord + parentDescent - pfd->mBounds.BSize(lineWM) + 2174 pfd->mBorderPadding.BEnd(lineWM) - frameSpan->mBEndLeading; 2175 } else { 2176 pfd->mBounds.BStart(lineWM) = baselineBCoord + parentDescent - 2177 pfd->mBounds.BSize(lineWM) - 2178 pfd->mMargin.BEnd(lineWM); 2179 } 2180 pfd->mBlockDirAlign = VALIGN_OTHER; 2181 break; 2182 } 2183 2184 case StyleVerticalAlignKeyword::MozMiddleWithBaseline: { 2185 // Align the midpoint of the frame with the baseline of the parent. 2186 if (frameSpan) { 2187 pfd->mBounds.BStart(lineWM) = 2188 baselineBCoord - pfd->mBounds.BSize(lineWM) / 2; 2189 } else { 2190 pfd->mBounds.BStart(lineWM) = 2191 baselineBCoord - logicalBSize / 2 + pfd->mMargin.BStart(lineWM); 2192 } 2193 pfd->mBlockDirAlign = VALIGN_OTHER; 2194 break; 2195 } 2196 } 2197 } else { 2198 // We have either a coord, a percent, or a calc(). 2199 nscoord offset = verticalAlign.AsLength().Resolve([&] { 2200 // Percentages are like lengths, except treated as a percentage 2201 // of the elements line block size value. 2202 float inflation = 2203 GetInflationForBlockDirAlignment(frame, mInflationMinFontSize); 2204 return ReflowInput::CalcLineHeight( 2205 *frame->Style(), frame->PresContext(), frame->GetContent(), 2206 mLineContainerRI.ComputedBSize(), inflation); 2207 }); 2208 2209 // According to the CSS2 spec (10.8.1), a positive value 2210 // "raises" the box by the given distance while a negative value 2211 // "lowers" the box by the given distance (with zero being the 2212 // baseline). Since Y coordinates increase towards the bottom of 2213 // the screen we reverse the sign, unless the line orientation is 2214 // inverted relative to block direction. 2215 nscoord revisedBaselineBCoord = 2216 baselineBCoord - offset * lineWM.FlowRelativeToLineRelativeFactor(); 2217 if (lineWM.IsCentralBaseline()) { 2218 // If we're using a dominant center baseline, we align with the center 2219 // of the frame being placed (bug 1133945). 2220 pfd->mBounds.BStart(lineWM) = 2221 revisedBaselineBCoord - pfd->mBounds.BSize(lineWM) / 2; 2222 } else { 2223 pfd->mBounds.BStart(lineWM) = revisedBaselineBCoord - pfd->mAscent; 2224 } 2225 pfd->mBlockDirAlign = VALIGN_OTHER; 2226 } 2227 2228 // Update minBCoord/maxBCoord for frames that we just placed. Do not factor 2229 // text into the equation. 2230 if (pfd->mBlockDirAlign == VALIGN_OTHER) { 2231 // Text frames do not contribute to the min/max Y values for the 2232 // line (instead their parent frame's font-size contributes). 2233 // XXXrbs -- relax this restriction because it causes text frames 2234 // to jam together when 'font-size-adjust' is enabled 2235 // and layout is using dynamic font heights (bug 20394) 2236 // -- Note #1: With this code enabled and with the fact that we are 2237 // not using Em[Ascent|Descent] as nsDimensions for text 2238 // metrics in GFX mean that the discussion in bug 13072 cannot 2239 // hold. 2240 // -- Note #2: We still don't want empty-text frames to interfere. 2241 // For example in quirks mode, avoiding empty text frames 2242 // prevents "tall" lines around elements like <hr> since the 2243 // rules of <hr> in quirks.css have pseudo text contents with LF 2244 // in them. 2245 bool canUpdate; 2246 if (pfd->mIsTextFrame) { 2247 // Only consider text frames if they're not empty and 2248 // line-height=normal. 2249 canUpdate = pfd->mIsNonWhitespaceTextFrame && 2250 frame->StyleFont()->mLineHeight.IsNormal(); 2251 } else { 2252 canUpdate = !pfd->mIsPlaceholder; 2253 } 2254 2255 if (canUpdate) { 2256 nscoord blockStart, blockEnd; 2257 if (frameSpan) { 2258 // For spans that were are now placing, use their position 2259 // plus their already computed min-Y and max-Y values for 2260 // computing blockStart and blockEnd. 2261 blockStart = pfd->mBounds.BStart(lineWM) + frameSpan->mMinBCoord; 2262 blockEnd = pfd->mBounds.BStart(lineWM) + frameSpan->mMaxBCoord; 2263 } else { 2264 blockStart = 2265 pfd->mBounds.BStart(lineWM) - pfd->mMargin.BStart(lineWM); 2266 blockEnd = blockStart + logicalBSize; 2267 } 2268 if (!preMode && 2269 mPresContext->CompatibilityMode() != eCompatibility_FullStandards && 2270 !logicalBSize) { 2271 // Check if it's a BR frame that is not alone on its line (it 2272 // is given a block size of zero to indicate this), and if so reset 2273 // blockStart and blockEnd so that BR frames don't influence the line. 2274 if (frame->IsBrFrame()) { 2275 blockStart = BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM; 2276 blockEnd = BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM; 2277 } 2278 } 2279 if (blockStart < minBCoord) { 2280 minBCoord = blockStart; 2281 } 2282 if (blockEnd > maxBCoord) { 2283 maxBCoord = blockEnd; 2284 } 2285 #ifdef NOISY_BLOCKDIR_ALIGN 2286 printf( 2287 " [frame]raw: a=%d h=%d bp=%d,%d logical: h=%d leading=%d y=%d " 2288 "minBCoord=%d maxBCoord=%d\n", 2289 pfd->mAscent, pfd->mBounds.BSize(lineWM), 2290 pfd->mBorderPadding.Top(lineWM), pfd->mBorderPadding.Bottom(lineWM), 2291 logicalBSize, frameSpan ? frameSpan->mBStartLeading : 0, 2292 pfd->mBounds.BStart(lineWM), minBCoord, maxBCoord); 2293 #endif 2294 } 2295 if (psd != mRootSpan) { 2296 frame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd)); 2297 } 2298 } 2299 pfd = pfd->mNext; 2300 } 2301 2302 // Factor in the minimum line block-size when handling the root-span for 2303 // the block. 2304 if (psd == mRootSpan) { 2305 // We should factor in the block element's minimum line-height (as 2306 // defined in section 10.8.1 of the css2 spec) assuming that 2307 // zeroEffectiveSpanBox is not set on the root span. This only happens 2308 // in some cases in quirks mode: 2309 // (1) if the root span contains non-whitespace text directly (this 2310 // is handled by zeroEffectiveSpanBox 2311 // (2) if this line has a ::marker 2312 // (3) if this is the last line of an LI, DT, or DD element 2313 // (The last line before a block also counts, but not before a 2314 // BR) (NN4/IE5 quirk) 2315 2316 // (1) and (2) above 2317 bool applyMinLH = !zeroEffectiveSpanBox || mHasMarker; 2318 bool isLastLine = 2319 !mGotLineBox || (!mLineBox->IsLineWrapped() && !mLineEndsInBR); 2320 if (!applyMinLH && isLastLine) { 2321 nsIContent* blockContent = mRootSpan->mFrame->mFrame->GetContent(); 2322 if (blockContent) { 2323 // (3) above, if the last line of LI, DT, or DD 2324 if (blockContent->IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::dt, 2325 nsGkAtoms::dd)) { 2326 applyMinLH = true; 2327 } 2328 } 2329 } 2330 if (applyMinLH) { 2331 if (psd->mHasNonemptyContent || 2332 (preMode && ShouldApplyLineHeightInPreserveWhiteSpace(psd)) || 2333 mHasMarker) { 2334 #ifdef NOISY_BLOCKDIR_ALIGN 2335 printf(" [span]==> adjusting min/maxBCoord: currentValues: %d,%d", 2336 minBCoord, maxBCoord); 2337 #endif 2338 nscoord minimumLineBSize = mMinLineBSize; 2339 nscoord blockStart = -nsLayoutUtils::GetCenteredFontBaseline( 2340 fm, minimumLineBSize, lineWM.IsLineInverted()); 2341 nscoord blockEnd = blockStart + minimumLineBSize; 2342 2343 if (mStyleText->HasEffectiveTextEmphasis()) { 2344 nscoord fontMaxHeight = 2345 mPresContext->NormalizeRubyMetrics() 2346 ? mPresContext->RubyPositioningFactor() * 2347 (fm->TrimmedAscent() + fm->TrimmedDescent()) 2348 : fm->MaxHeight(); 2349 nscoord emphasisHeight = 2350 GetBSizeOfEmphasisMarks(spanFrame, inflation); 2351 nscoord delta = fontMaxHeight + emphasisHeight - minimumLineBSize; 2352 if (delta > 0) { 2353 if (minimumLineBSize < fontMaxHeight) { 2354 // If the leadings are negative, fill them first. 2355 nscoord ascent = fm->MaxAscent(); 2356 nscoord descent = fm->MaxDescent(); 2357 if (lineWM.IsLineInverted()) { 2358 std::swap(ascent, descent); 2359 } 2360 blockStart = -ascent; 2361 blockEnd = descent; 2362 delta = emphasisHeight; 2363 } 2364 LogicalSide side = mStyleText->TextEmphasisSide( 2365 lineWM, spanFrame->StyleFont()->mLanguage); 2366 if (side == LogicalSide::BStart) { 2367 blockStart -= delta; 2368 } else { 2369 blockEnd += delta; 2370 } 2371 } 2372 } 2373 2374 minBCoord = std::min(blockStart, minBCoord); 2375 maxBCoord = std::max(blockEnd, maxBCoord); 2376 2377 #ifdef NOISY_BLOCKDIR_ALIGN 2378 printf(" new values: %d,%d\n", minBCoord, maxBCoord); 2379 #endif 2380 #ifdef NOISY_BLOCKDIR_ALIGN 2381 printf( 2382 " Used mMinLineBSize: %d, blockStart: %d, blockEnd: " 2383 "%d\n", 2384 mMinLineBSize, blockStart, blockEnd); 2385 #endif 2386 } else { 2387 // XXX issues: 2388 // [1] BR's on empty lines stop working 2389 // [2] May not honor css2's notion of handling empty elements 2390 // [3] blank lines in a pre-section ("\n") (handled with preMode) 2391 2392 // XXX Are there other problems with this? 2393 #ifdef NOISY_BLOCKDIR_ALIGN 2394 printf( 2395 " [span]==> zapping min/maxBCoord: currentValues: %d,%d " 2396 "newValues: 0,0\n", 2397 minBCoord, maxBCoord); 2398 #endif 2399 minBCoord = maxBCoord = 0; 2400 } 2401 } 2402 } 2403 2404 if ((minBCoord == BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM) || 2405 (maxBCoord == BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM)) { 2406 minBCoord = maxBCoord = baselineBCoord; 2407 } 2408 2409 if (psd != mRootSpan && zeroEffectiveSpanBox) { 2410 #ifdef NOISY_BLOCKDIR_ALIGN 2411 printf(" [span]adjusting for zeroEffectiveSpanBox\n"); 2412 printf( 2413 " Original: minBCoord=%d, maxBCoord=%d, bSize=%d, ascent=%d, " 2414 "logicalBSize=%d, topLeading=%d, bottomLeading=%d\n", 2415 minBCoord, maxBCoord, spanFramePFD->mBounds.BSize(lineWM), 2416 spanFramePFD->mAscent, psd->mLogicalBSize, psd->mBStartLeading, 2417 psd->mBEndLeading); 2418 #endif 2419 nscoord goodMinBCoord = 2420 spanFramePFD->mBorderPadding.BStart(lineWM) - psd->mBStartLeading; 2421 nscoord goodMaxBCoord = goodMinBCoord + psd->mLogicalBSize; 2422 2423 // For cases like the one in bug 714519 (text-decoration placement 2424 // or making nsLineLayout::IsZeroBSize() handle 2425 // vertical-align:top/bottom on a descendant of the line that's not 2426 // a child of it), we want to treat elements that are 2427 // vertical-align: top or bottom somewhat like children for the 2428 // purposes of this quirk. To some extent, this is guessing, since 2429 // they might end up being aligned anywhere. However, we'll guess 2430 // that they'll be placed aligned with the top or bottom of this 2431 // frame (as though this frame is the only thing in the line). 2432 // (Guessing isn't unreasonable, since all we're doing is reducing the 2433 // scope of a quirk and making the behavior more standards-like.) 2434 if (maxStartBoxBSize > maxBCoord - minBCoord) { 2435 // Distribute maxStartBoxBSize to ascent (baselineBCoord - minBCoord), and 2436 // then to descent (maxBCoord - baselineBCoord) by adjusting minBCoord or 2437 // maxBCoord, but not to exceed goodMinBCoord and goodMaxBCoord. 2438 nscoord distribute = maxStartBoxBSize - (maxBCoord - minBCoord); 2439 nscoord ascentSpace = std::max(minBCoord - goodMinBCoord, 0); 2440 if (distribute > ascentSpace) { 2441 distribute -= ascentSpace; 2442 minBCoord -= ascentSpace; 2443 nscoord descentSpace = std::max(goodMaxBCoord - maxBCoord, 0); 2444 maxBCoord += std::min(descentSpace, distribute); 2445 } else { 2446 minBCoord -= distribute; 2447 } 2448 } 2449 if (maxEndBoxBSize > maxBCoord - minBCoord) { 2450 // Likewise, but preferring descent to ascent. 2451 nscoord distribute = maxEndBoxBSize - (maxBCoord - minBCoord); 2452 nscoord descentSpace = std::max(goodMaxBCoord - maxBCoord, 0); 2453 if (distribute > descentSpace) { 2454 distribute -= descentSpace; 2455 maxBCoord += descentSpace; 2456 nscoord ascentSpace = std::max(minBCoord - goodMinBCoord, 0); 2457 minBCoord -= std::min(ascentSpace, distribute); 2458 } else { 2459 maxBCoord += distribute; 2460 } 2461 } 2462 2463 if (minBCoord > goodMinBCoord) { 2464 nscoord adjust = minBCoord - goodMinBCoord; // positive 2465 2466 // shrink the logical extents 2467 psd->mLogicalBSize -= adjust; 2468 psd->mBStartLeading -= adjust; 2469 } 2470 if (maxBCoord < goodMaxBCoord) { 2471 nscoord adjust = goodMaxBCoord - maxBCoord; 2472 psd->mLogicalBSize -= adjust; 2473 psd->mBEndLeading -= adjust; 2474 } 2475 if (minBCoord > 0) { 2476 // shrink the content by moving its block start down. This is tricky, 2477 // since the block start is the 0 for many coordinates, so what we do is 2478 // move everything else up. 2479 spanFramePFD->mAscent -= minBCoord; // move the baseline up 2480 spanFramePFD->mBounds.BSize(lineWM) -= 2481 minBCoord; // move the block end up 2482 psd->mBStartLeading += minBCoord; 2483 *psd->mBaseline -= minBCoord; 2484 2485 pfd = psd->mFirstFrame; 2486 while (nullptr != pfd) { 2487 pfd->mBounds.BStart(lineWM) -= minBCoord; // move all the children 2488 // back up 2489 pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd)); 2490 pfd = pfd->mNext; 2491 } 2492 maxBCoord -= minBCoord; // since minBCoord is in the frame's own 2493 // coordinate system 2494 minBCoord = 0; 2495 } 2496 if (maxBCoord < spanFramePFD->mBounds.BSize(lineWM)) { 2497 nscoord adjust = spanFramePFD->mBounds.BSize(lineWM) - maxBCoord; 2498 spanFramePFD->mBounds.BSize(lineWM) -= adjust; // move the bottom up 2499 psd->mBEndLeading += adjust; 2500 } 2501 #ifdef NOISY_BLOCKDIR_ALIGN 2502 printf( 2503 " New: minBCoord=%d, maxBCoord=%d, bSize=%d, ascent=%d, " 2504 "logicalBSize=%d, topLeading=%d, bottomLeading=%d\n", 2505 minBCoord, maxBCoord, spanFramePFD->mBounds.BSize(lineWM), 2506 spanFramePFD->mAscent, psd->mLogicalBSize, psd->mBStartLeading, 2507 psd->mBEndLeading); 2508 #endif 2509 } 2510 2511 psd->mMinBCoord = minBCoord; 2512 psd->mMaxBCoord = maxBCoord; 2513 #ifdef NOISY_BLOCKDIR_ALIGN 2514 printf( 2515 " [span]==> minBCoord=%d maxBCoord=%d delta=%d maxStartBoxBSize=%d " 2516 "maxEndBoxBSize=%d\n", 2517 minBCoord, maxBCoord, maxBCoord - minBCoord, maxStartBoxBSize, 2518 maxEndBoxBSize); 2519 #endif 2520 mMaxStartBoxBSize = std::max(mMaxStartBoxBSize, maxStartBoxBSize); 2521 mMaxEndBoxBSize = std::max(mMaxEndBoxBSize, maxEndBoxBSize); 2522 } 2523 2524 static void SlideSpanFrameRect(nsIFrame* aFrame, nscoord aDeltaWidth) { 2525 // This should not use nsIFrame::MovePositionBy because it happens 2526 // prior to relative positioning. In particular, because 2527 // nsBlockFrame::PlaceLine calls aLineLayout.TrimTrailingWhiteSpace() 2528 // prior to calling aLineLayout.RelativePositionFrames(). 2529 nsPoint p = aFrame->GetPosition(); 2530 p.x -= aDeltaWidth; 2531 aFrame->SetPosition(p); 2532 } 2533 2534 bool nsLineLayout::TrimTrailingWhiteSpaceIn(PerSpanData* psd, 2535 nscoord* aDeltaISize) { 2536 PerFrameData* pfd = psd->mFirstFrame; 2537 if (!pfd) { 2538 *aDeltaISize = 0; 2539 return false; 2540 } 2541 pfd = pfd->Last(); 2542 while (nullptr != pfd) { 2543 #ifdef REALLY_NOISY_TRIM 2544 psd->mFrame->mFrame->ListTag(stdout); 2545 printf(": attempting trim of "); 2546 pfd->mFrame->ListTag(stdout); 2547 printf("\n"); 2548 #endif 2549 PerSpanData* childSpan = pfd->mSpan; 2550 WritingMode lineWM = mRootSpan->mWritingMode; 2551 if (childSpan) { 2552 // Maybe the child span has the trailing white-space in it? 2553 if (TrimTrailingWhiteSpaceIn(childSpan, aDeltaISize)) { 2554 nscoord deltaISize = *aDeltaISize; 2555 if (deltaISize) { 2556 // Adjust the child spans frame size 2557 pfd->mBounds.ISize(lineWM) -= deltaISize; 2558 if (psd != mRootSpan) { 2559 // When the child span is not a direct child of the block 2560 // we need to update the child spans frame rectangle 2561 // because it most likely will not be done again. Spans 2562 // that are direct children of the block will be updated 2563 // later, however, because the VerticalAlignFrames method 2564 // will be run after this method. 2565 nsSize containerSize = ContainerSizeForSpan(childSpan); 2566 nsIFrame* f = pfd->mFrame; 2567 LogicalRect r(lineWM, f->GetRect(), containerSize); 2568 r.ISize(lineWM) -= deltaISize; 2569 f->SetRect(lineWM, r, containerSize); 2570 } 2571 2572 // Adjust the inline end edge of the span that contains the child span 2573 psd->mICoord -= deltaISize; 2574 2575 // Slide any frames that follow the child span over by the 2576 // correct amount. The only thing that can follow the child 2577 // span is empty stuff, so we are just making things 2578 // sensible (keeping the combined area honest). 2579 while (pfd->mNext) { 2580 pfd = pfd->mNext; 2581 pfd->mBounds.IStart(lineWM) -= deltaISize; 2582 if (psd != mRootSpan) { 2583 // When the child span is not a direct child of the block 2584 // we need to update the child span's frame rectangle 2585 // because it most likely will not be done again. Spans 2586 // that are direct children of the block will be updated 2587 // later, however, because the VerticalAlignFrames method 2588 // will be run after this method. 2589 SlideSpanFrameRect(pfd->mFrame, deltaISize); 2590 } 2591 } 2592 } 2593 return true; 2594 } 2595 } else if (!pfd->mIsTextFrame && !pfd->mSkipWhenTrimmingWhitespace) { 2596 // If we hit a frame on the end that's not text and not a placeholder, 2597 // then there is no trailing whitespace to trim. Stop the search. 2598 *aDeltaISize = 0; 2599 return true; 2600 } else if (pfd->mIsTextFrame) { 2601 // Call TrimTrailingWhiteSpace even on empty textframes because they 2602 // might have a soft hyphen which should now appear, changing the frame's 2603 // width 2604 nsTextFrame::TrimOutput trimOutput = 2605 static_cast<nsTextFrame*>(pfd->mFrame) 2606 ->TrimTrailingWhiteSpace( 2607 mLineContainerRI.mRenderingContext->GetDrawTarget()); 2608 #ifdef NOISY_TRIM 2609 psd->mFrame->mFrame->ListTag(stdout); 2610 printf(": trim of "); 2611 pfd->mFrame->ListTag(stdout); 2612 printf(" returned %d\n", trimOutput.mDeltaWidth); 2613 #endif 2614 2615 if (trimOutput.mChanged) { 2616 pfd->mRecomputeOverflow = true; 2617 } 2618 2619 // Delta width not being zero means that 2620 // there is trimmed space in the frame. 2621 if (trimOutput.mDeltaWidth) { 2622 pfd->mBounds.ISize(lineWM) -= trimOutput.mDeltaWidth; 2623 2624 // If any trailing space is trimmed, the justification opportunity 2625 // generated by the space should be removed as well. 2626 pfd->mJustificationInfo.CancelOpportunityForTrimmedSpace(); 2627 2628 // See if the text frame has already been placed in its parent 2629 if (psd != mRootSpan) { 2630 // The frame was already placed during psd's 2631 // reflow. Update the frames rectangle now. 2632 pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd)); 2633 } 2634 2635 // Adjust containing span's right edge 2636 psd->mICoord -= trimOutput.mDeltaWidth; 2637 2638 // Slide any frames that follow the text frame over by the 2639 // right amount. The only thing that can follow the text 2640 // frame is empty stuff, so we are just making things 2641 // sensible (keeping the combined area honest). 2642 while (pfd->mNext) { 2643 pfd = pfd->mNext; 2644 pfd->mBounds.IStart(lineWM) -= trimOutput.mDeltaWidth; 2645 if (psd != mRootSpan) { 2646 // When the child span is not a direct child of the block 2647 // we need to update the child spans frame rectangle 2648 // because it most likely will not be done again. Spans 2649 // that are direct children of the block will be updated 2650 // later, however, because the VerticalAlignFrames method 2651 // will be run after this method. 2652 SlideSpanFrameRect(pfd->mFrame, trimOutput.mDeltaWidth); 2653 } 2654 } 2655 } 2656 2657 if (pfd->mIsNonEmptyTextFrame || trimOutput.mChanged) { 2658 // Pass up to caller so they can shrink their span 2659 *aDeltaISize = trimOutput.mDeltaWidth; 2660 return true; 2661 } 2662 } 2663 pfd = pfd->mPrev; 2664 } 2665 2666 *aDeltaISize = 0; 2667 return false; 2668 } 2669 2670 bool nsLineLayout::TrimTrailingWhiteSpace() { 2671 nscoord deltaISize; 2672 TrimTrailingWhiteSpaceIn(mRootSpan, &deltaISize); 2673 return 0 != deltaISize; 2674 } 2675 2676 bool nsLineLayout::PerFrameData::ParticipatesInJustification() const { 2677 if (mIsMarker || mIsEmpty || mSkipWhenTrimmingWhitespace) { 2678 // Skip ::markers, empty frames, and placeholders 2679 return false; 2680 } 2681 if (mIsTextFrame && !mIsNonWhitespaceTextFrame && 2682 static_cast<nsTextFrame*>(mFrame)->IsAtEndOfLine()) { 2683 // Skip trimmed whitespaces 2684 return false; 2685 } 2686 return true; 2687 } 2688 2689 struct nsLineLayout::JustificationComputationState { 2690 PerFrameData* mFirstParticipant; 2691 PerFrameData* mLastParticipant; 2692 // When we are going across a boundary of ruby base, i.e. entering 2693 // one, leaving one, or both, the following fields will be set to 2694 // the corresponding ruby base frame for handling ruby-align. 2695 PerFrameData* mLastExitedRubyBase; 2696 PerFrameData* mLastEnteredRubyBase; 2697 2698 JustificationComputationState() 2699 : mFirstParticipant(nullptr), 2700 mLastParticipant(nullptr), 2701 mLastExitedRubyBase(nullptr), 2702 mLastEnteredRubyBase(nullptr) {} 2703 }; 2704 2705 static bool IsRubyAlignSpaceAround(nsIFrame* aRubyBase) { 2706 return aRubyBase->StyleText()->mRubyAlign == StyleRubyAlign::SpaceAround; 2707 } 2708 2709 /** 2710 * Assign justification gaps for justification 2711 * opportunities across two frames. 2712 */ 2713 /* static */ 2714 int nsLineLayout::AssignInterframeJustificationGaps( 2715 PerFrameData* aFrame, JustificationComputationState& aState) { 2716 PerFrameData* prev = aState.mLastParticipant; 2717 MOZ_ASSERT(prev); 2718 2719 auto& assign = aFrame->mJustificationAssignment; 2720 auto& prevAssign = prev->mJustificationAssignment; 2721 2722 if (aState.mLastExitedRubyBase || aState.mLastEnteredRubyBase) { 2723 PerFrameData* exitedRubyBase = aState.mLastExitedRubyBase; 2724 if (!exitedRubyBase || IsRubyAlignSpaceAround(exitedRubyBase->mFrame)) { 2725 prevAssign.mGapsAtEnd = 1; 2726 } else { 2727 exitedRubyBase->mJustificationAssignment.mGapsAtEnd = 1; 2728 } 2729 2730 PerFrameData* enteredRubyBase = aState.mLastEnteredRubyBase; 2731 if (!enteredRubyBase || IsRubyAlignSpaceAround(enteredRubyBase->mFrame)) { 2732 assign.mGapsAtStart = 1; 2733 } else { 2734 enteredRubyBase->mJustificationAssignment.mGapsAtStart = 1; 2735 } 2736 2737 // We are no longer going across a ruby base boundary. 2738 aState.mLastExitedRubyBase = nullptr; 2739 aState.mLastEnteredRubyBase = nullptr; 2740 return 1; 2741 } 2742 2743 const auto& info = aFrame->mJustificationInfo; 2744 const auto& prevInfo = prev->mJustificationInfo; 2745 if (!info.mIsStartJustifiable && !prevInfo.mIsEndJustifiable) { 2746 return 0; 2747 } 2748 2749 if (!info.mIsStartJustifiable) { 2750 prevAssign.mGapsAtEnd = 2; 2751 assign.mGapsAtStart = 0; 2752 } else if (!prevInfo.mIsEndJustifiable) { 2753 prevAssign.mGapsAtEnd = 0; 2754 assign.mGapsAtStart = 2; 2755 } else { 2756 prevAssign.mGapsAtEnd = 1; 2757 assign.mGapsAtStart = 1; 2758 } 2759 return 1; 2760 } 2761 2762 /** 2763 * Compute the justification info of the given span, and store the 2764 * number of inner opportunities into the frame's justification info. 2765 * It returns the number of non-inner opportunities it detects. 2766 */ 2767 int32_t nsLineLayout::ComputeFrameJustification( 2768 PerSpanData* aPSD, JustificationComputationState& aState) { 2769 NS_ASSERTION(aPSD, "null arg"); 2770 NS_ASSERTION(!aState.mLastParticipant || !aState.mLastParticipant->mSpan, 2771 "Last participant shall always be a leaf frame"); 2772 bool firstChild = true; 2773 int32_t& innerOpportunities = 2774 aPSD->mFrame->mJustificationInfo.mInnerOpportunities; 2775 MOZ_ASSERT(innerOpportunities == 0, 2776 "Justification info should not have been set yet."); 2777 int32_t outerOpportunities = 0; 2778 2779 for (PerFrameData* pfd = aPSD->mFirstFrame; pfd; pfd = pfd->mNext) { 2780 if (!pfd->ParticipatesInJustification()) { 2781 continue; 2782 } 2783 2784 bool isRubyBase = pfd->mFrame->IsRubyBaseFrame(); 2785 PerFrameData* outerRubyBase = aState.mLastEnteredRubyBase; 2786 if (isRubyBase) { 2787 aState.mLastEnteredRubyBase = pfd; 2788 } 2789 2790 int extraOpportunities = 0; 2791 if (pfd->mSpan) { 2792 PerSpanData* span = pfd->mSpan; 2793 extraOpportunities = ComputeFrameJustification(span, aState); 2794 innerOpportunities += pfd->mJustificationInfo.mInnerOpportunities; 2795 } else { 2796 if (pfd->mIsTextFrame) { 2797 innerOpportunities += pfd->mJustificationInfo.mInnerOpportunities; 2798 } 2799 2800 if (!aState.mLastParticipant) { 2801 aState.mFirstParticipant = pfd; 2802 // It is not an empty ruby base, but we are not assigning gaps 2803 // to the content for now. Clear the last entered ruby base so 2804 // that we can correctly set the last exited ruby base. 2805 aState.mLastEnteredRubyBase = nullptr; 2806 } else { 2807 extraOpportunities = AssignInterframeJustificationGaps(pfd, aState); 2808 } 2809 2810 aState.mLastParticipant = pfd; 2811 } 2812 2813 if (isRubyBase) { 2814 if (aState.mLastEnteredRubyBase == pfd) { 2815 // There is no justification participant inside this ruby base. 2816 // Ignore this ruby base completely and restore the outer ruby 2817 // base here. 2818 aState.mLastEnteredRubyBase = outerRubyBase; 2819 } else { 2820 aState.mLastExitedRubyBase = pfd; 2821 } 2822 } 2823 2824 if (firstChild) { 2825 outerOpportunities = extraOpportunities; 2826 firstChild = false; 2827 } else { 2828 innerOpportunities += extraOpportunities; 2829 } 2830 } 2831 2832 return outerOpportunities; 2833 } 2834 2835 void nsLineLayout::AdvanceAnnotationInlineBounds(PerFrameData* aPFD, 2836 const nsSize& aContainerSize, 2837 nscoord aDeltaICoord, 2838 nscoord aDeltaISize) { 2839 nsIFrame* frame = aPFD->mFrame; 2840 LayoutFrameType frameType = frame->Type(); 2841 MOZ_ASSERT(frameType == LayoutFrameType::RubyText || 2842 frameType == LayoutFrameType::RubyTextContainer); 2843 MOZ_ASSERT(aPFD->mSpan, "rt and rtc should have span."); 2844 2845 PerSpanData* psd = aPFD->mSpan; 2846 WritingMode lineWM = mRootSpan->mWritingMode; 2847 aPFD->mBounds.IStart(lineWM) += aDeltaICoord; 2848 2849 // Check whether this expansion should be counted into the reserved 2850 // isize or not. When it is a ruby text container, and it has some 2851 // children linked to the base, it must not have reserved isize, 2852 // or its children won't align with their bases. Otherwise, this 2853 // expansion should be reserved. There are two cases a ruby text 2854 // container does not have children linked to the base: 2855 // 1. it is a container for span; 2. its children are collapsed. 2856 // See bug 1055674 for the second case. 2857 if (frameType == LayoutFrameType::RubyText || 2858 // This ruby text container is a span. 2859 (psd->mFirstFrame == psd->mLastFrame && psd->mFirstFrame && 2860 !psd->mFirstFrame->mIsLinkedToBase)) { 2861 // For ruby text frames, only increase frames 2862 // which are not auto-hidden. 2863 if (frameType != LayoutFrameType::RubyText || 2864 !static_cast<nsRubyTextFrame*>(frame)->IsCollapsed()) { 2865 nscoord reservedISize = RubyUtils::GetReservedISize(frame); 2866 RubyUtils::SetReservedISize(frame, reservedISize + aDeltaISize); 2867 } 2868 } else { 2869 // It is a normal ruby text container. Its children will expand 2870 // themselves properly. We only need to expand its own size here. 2871 aPFD->mBounds.ISize(lineWM) += aDeltaISize; 2872 } 2873 aPFD->mFrame->SetRect(lineWM, aPFD->mBounds, aContainerSize); 2874 } 2875 2876 /** 2877 * This function applies the changes of icoord and isize caused by 2878 * justification to annotations of the given frame. 2879 */ 2880 void nsLineLayout::ApplyLineJustificationToAnnotations(PerFrameData* aPFD, 2881 nscoord aDeltaICoord, 2882 nscoord aDeltaISize) { 2883 for (auto* pfd = aPFD->mNextAnnotation; pfd; pfd = pfd->mNextAnnotation) { 2884 nsSize containerSize = pfd->mFrame->GetParent()->GetSize(); 2885 AdvanceAnnotationInlineBounds(pfd, containerSize, aDeltaICoord, 2886 aDeltaISize); 2887 2888 // There are two cases where an annotation frame has siblings which 2889 // do not attached to a ruby base-level frame: 2890 // 1. there's an intra-annotation whitespace which has no intra-base 2891 // white-space to pair with; 2892 // 2. there are not enough ruby bases to be paired with annotations. 2893 // In these cases, their size should not be affected, but we still 2894 // need to move them so that they won't overlap other frames. 2895 for (auto* sibling = pfd->mNext; sibling && !sibling->mIsLinkedToBase; 2896 sibling = sibling->mNext) { 2897 AdvanceAnnotationInlineBounds(sibling, containerSize, 2898 aDeltaICoord + aDeltaISize, 0); 2899 } 2900 } 2901 } 2902 2903 nscoord nsLineLayout::ApplyFrameJustification( 2904 PerSpanData* aPSD, JustificationApplicationState& aState) { 2905 NS_ASSERTION(aPSD, "null arg"); 2906 2907 nscoord deltaICoord = 0; 2908 for (PerFrameData* pfd = aPSD->mFirstFrame; pfd; pfd = pfd->mNext) { 2909 nscoord dw = 0; 2910 WritingMode lineWM = mRootSpan->mWritingMode; 2911 const auto& assign = pfd->mJustificationAssignment; 2912 bool isInlineText = 2913 pfd->mIsTextFrame && !pfd->mWritingMode.IsOrthogonalTo(lineWM); 2914 2915 // Don't apply justification if the frame doesn't participate. Same 2916 // as the condition used in ComputeFrameJustification. Note that, 2917 // we still need to move the frame based on deltaICoord even if the 2918 // frame itself doesn't expand. 2919 if (pfd->ParticipatesInJustification()) { 2920 if (isInlineText) { 2921 if (aState.IsJustifiable()) { 2922 // Set corresponding justification gaps here, so that the 2923 // text frame knows how it should add gaps at its sides. 2924 const auto& info = pfd->mJustificationInfo; 2925 auto textFrame = static_cast<nsTextFrame*>(pfd->mFrame); 2926 textFrame->AssignJustificationGaps(assign); 2927 dw = aState.Consume(JustificationUtils::CountGaps(info, assign)); 2928 } 2929 2930 if (dw) { 2931 pfd->mRecomputeOverflow = true; 2932 } 2933 } else { 2934 if (nullptr != pfd->mSpan) { 2935 dw = ApplyFrameJustification(pfd->mSpan, aState); 2936 } 2937 } 2938 } else { 2939 MOZ_ASSERT(!assign.TotalGaps(), 2940 "Non-participants shouldn't have assigned gaps"); 2941 } 2942 2943 pfd->mBounds.ISize(lineWM) += dw; 2944 nscoord gapsAtEnd = 0; 2945 if (!isInlineText && assign.TotalGaps()) { 2946 // It is possible that we assign gaps to non-text frame or an 2947 // orthogonal text frame. Apply the gaps as margin for them. 2948 deltaICoord += aState.Consume(assign.mGapsAtStart); 2949 gapsAtEnd = aState.Consume(assign.mGapsAtEnd); 2950 dw += gapsAtEnd; 2951 } 2952 pfd->mBounds.IStart(lineWM) += deltaICoord; 2953 2954 // The gaps added to the end of the frame should also be 2955 // excluded from the isize added to the annotation. 2956 ApplyLineJustificationToAnnotations(pfd, deltaICoord, dw - gapsAtEnd); 2957 deltaICoord += dw; 2958 pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(aPSD)); 2959 } 2960 return deltaICoord; 2961 } 2962 2963 static nsIFrame* FindNearestRubyBaseAncestor(nsIFrame* aFrame) { 2964 MOZ_ASSERT(aFrame->Style()->ShouldSuppressLineBreak()); 2965 while (aFrame && !aFrame->IsRubyBaseFrame()) { 2966 aFrame = aFrame->GetParent(); 2967 } 2968 // XXX It is possible that no ruby base ancestor is found because of 2969 // some edge cases like form control or canvas inside ruby text. 2970 // See bug 1138092 comment 4. 2971 NS_WARNING_ASSERTION(aFrame, "no ruby base ancestor?"); 2972 return aFrame; 2973 } 2974 2975 /** 2976 * This method expands the given frame by the given reserved isize. 2977 */ 2978 void nsLineLayout::ExpandRubyBox(PerFrameData* aFrame, nscoord aReservedISize, 2979 const nsSize& aContainerSize) { 2980 WritingMode lineWM = mRootSpan->mWritingMode; 2981 auto rubyAlign = aFrame->mFrame->StyleText()->mRubyAlign; 2982 switch (rubyAlign) { 2983 case StyleRubyAlign::Start: 2984 // do nothing for start 2985 break; 2986 case StyleRubyAlign::SpaceBetween: 2987 case StyleRubyAlign::SpaceAround: { 2988 int32_t opportunities = aFrame->mJustificationInfo.mInnerOpportunities; 2989 int32_t gaps = opportunities * 2; 2990 if (rubyAlign == StyleRubyAlign::SpaceAround) { 2991 // Each expandable ruby box with ruby-align space-around has a 2992 // gap at each of its sides. For rb/rbc, see comment in 2993 // AssignInterframeJustificationGaps; for rt/rtc, see comment 2994 // in ExpandRubyBoxWithAnnotations. 2995 gaps += 2; 2996 } 2997 if (gaps > 0) { 2998 JustificationApplicationState state(gaps, aReservedISize); 2999 ApplyFrameJustification(aFrame->mSpan, state); 3000 break; 3001 } 3002 // If there are no justification opportunities for space-between, 3003 // fall-through to center per spec. 3004 [[fallthrough]]; 3005 } 3006 case StyleRubyAlign::Center: 3007 // Indent all children by half of the reserved inline size. 3008 for (PerFrameData* child = aFrame->mSpan->mFirstFrame; child; 3009 child = child->mNext) { 3010 child->mBounds.IStart(lineWM) += aReservedISize / 2; 3011 child->mFrame->SetRect(lineWM, child->mBounds, aContainerSize); 3012 } 3013 break; 3014 default: 3015 MOZ_ASSERT_UNREACHABLE("Unknown ruby-align value"); 3016 } 3017 3018 aFrame->mBounds.ISize(lineWM) += aReservedISize; 3019 aFrame->mFrame->SetRect(lineWM, aFrame->mBounds, aContainerSize); 3020 } 3021 3022 /** 3023 * This method expands the given frame by the reserved inline size. 3024 * It also expands its annotations if they are expandable and have 3025 * reserved isize larger than zero. 3026 */ 3027 void nsLineLayout::ExpandRubyBoxWithAnnotations(PerFrameData* aFrame, 3028 const nsSize& aContainerSize) { 3029 nscoord reservedISize = RubyUtils::GetReservedISize(aFrame->mFrame); 3030 if (reservedISize) { 3031 ExpandRubyBox(aFrame, reservedISize, aContainerSize); 3032 } 3033 3034 WritingMode lineWM = mRootSpan->mWritingMode; 3035 bool isLevelContainer = aFrame->mFrame->IsRubyBaseContainerFrame(); 3036 for (PerFrameData* annotation = aFrame->mNextAnnotation; annotation; 3037 annotation = annotation->mNextAnnotation) { 3038 if (lineWM.IsOrthogonalTo(annotation->mFrame->GetWritingMode())) { 3039 // Inter-character case: don't attempt to expand ruby annotations. 3040 continue; 3041 } 3042 if (isLevelContainer) { 3043 nsIFrame* rtcFrame = annotation->mFrame; 3044 MOZ_ASSERT(rtcFrame->IsRubyTextContainerFrame()); 3045 // It is necessary to set the rect again because the container 3046 // width was unknown, and zero was used instead when we reflow 3047 // them. The corresponding base containers were repositioned in 3048 // VerticalAlignFrames and PlaceTopBottomFrames. 3049 MOZ_ASSERT(rtcFrame->GetLogicalSize(lineWM) == 3050 annotation->mBounds.Size(lineWM)); 3051 rtcFrame->SetPosition(lineWM, annotation->mBounds.Origin(lineWM), 3052 aContainerSize); 3053 } 3054 3055 nscoord reservedISize = RubyUtils::GetReservedISize(annotation->mFrame); 3056 if (!reservedISize) { 3057 continue; 3058 } 3059 3060 MOZ_ASSERT(annotation->mSpan); 3061 JustificationComputationState computeState; 3062 ComputeFrameJustification(annotation->mSpan, computeState); 3063 if (!computeState.mFirstParticipant) { 3064 continue; 3065 } 3066 if (IsRubyAlignSpaceAround(annotation->mFrame)) { 3067 // Add one gap at each side of this annotation. 3068 computeState.mFirstParticipant->mJustificationAssignment.mGapsAtStart = 1; 3069 computeState.mLastParticipant->mJustificationAssignment.mGapsAtEnd = 1; 3070 } 3071 nsIFrame* parentFrame = annotation->mFrame->GetParent(); 3072 nsSize containerSize = parentFrame->GetSize(); 3073 MOZ_ASSERT(containerSize == aContainerSize || 3074 parentFrame->IsRubyTextContainerFrame(), 3075 "Container width should only be different when the current " 3076 "annotation is a ruby text frame, whose parent is not same " 3077 "as its base frame."); 3078 ExpandRubyBox(annotation, reservedISize, containerSize); 3079 ExpandInlineRubyBoxes(annotation->mSpan); 3080 } 3081 } 3082 3083 /** 3084 * This method looks for all expandable ruby box in the given span, and 3085 * calls ExpandRubyBox to expand them in depth-first preorder. 3086 */ 3087 void nsLineLayout::ExpandInlineRubyBoxes(PerSpanData* aSpan) { 3088 nsSize containerSize = ContainerSizeForSpan(aSpan); 3089 for (PerFrameData* pfd = aSpan->mFirstFrame; pfd; pfd = pfd->mNext) { 3090 if (RubyUtils::IsExpandableRubyBox(pfd->mFrame)) { 3091 ExpandRubyBoxWithAnnotations(pfd, containerSize); 3092 } 3093 if (pfd->mSpan) { 3094 ExpandInlineRubyBoxes(pfd->mSpan); 3095 } 3096 } 3097 } 3098 3099 nscoord nsLineLayout::GetHangFrom(const PerSpanData* aSpan, 3100 bool aLineIsRTL) const { 3101 const PerFrameData* pfd = aSpan->mLastFrame; 3102 nscoord result = 0; 3103 while (pfd) { 3104 if (const PerSpanData* childSpan = pfd->mSpan) { 3105 return GetHangFrom(childSpan, aLineIsRTL); 3106 } 3107 if (pfd->mIsTextFrame) { 3108 auto* lastText = static_cast<nsTextFrame*>(pfd->mFrame); 3109 result = lastText->GetHangableISize(); 3110 if (result) { 3111 // If the hangable space will be at the start edge of the line, due to 3112 // its bidi direction being against the line direction, we flag this by 3113 // negating the advance. 3114 lastText->EnsureTextRun(nsTextFrame::eInflated); 3115 auto* textRun = lastText->GetTextRun(nsTextFrame::eInflated); 3116 if (textRun && textRun->IsRightToLeft() != aLineIsRTL) { 3117 result = -result; 3118 } 3119 } 3120 return result; 3121 } 3122 if (!pfd->mSkipWhenTrimmingWhitespace) { 3123 // If we hit a frame on the end that's not text and not a placeholder or 3124 // <br>, then there is no trailing whitespace to hang. Stop the search. 3125 return result; 3126 } 3127 // Scan back for a preceding frame whose whitespace we can hang. 3128 pfd = pfd->mPrev; 3129 } 3130 return result; 3131 } 3132 3133 gfxTextRun::TrimmableWS nsLineLayout::GetTrimFrom(const PerSpanData* aSpan, 3134 bool aLineIsRTL) const { 3135 const PerFrameData* pfd = aSpan->mLastFrame; 3136 while (pfd) { 3137 if (const PerSpanData* childSpan = pfd->mSpan) { 3138 return GetTrimFrom(childSpan, aLineIsRTL); 3139 } 3140 if (pfd->mIsTextFrame) { 3141 auto* lastText = static_cast<nsTextFrame*>(pfd->mFrame); 3142 auto result = lastText->GetTrimmableWS(); 3143 if (result.mAdvance) { 3144 lastText->EnsureTextRun(nsTextFrame::eInflated); 3145 auto* textRun = lastText->GetTextRun(nsTextFrame::eInflated); 3146 if (textRun && textRun->IsRightToLeft() != aLineIsRTL) { 3147 result.mAdvance = -result.mAdvance; 3148 } 3149 } 3150 return result; 3151 } 3152 if (!pfd->mSkipWhenTrimmingWhitespace) { 3153 // If we hit a frame on the end that's not text and not a placeholder or 3154 // <br>, then there is no trailing whitespace to trim. Stop the search. 3155 return gfxTextRun::TrimmableWS{}; 3156 } 3157 // Scan back for a preceding frame whose whitespace we can trim. 3158 pfd = pfd->mPrev; 3159 } 3160 return gfxTextRun::TrimmableWS{}; 3161 } 3162 3163 // Align inline frames within the line according to the CSS text-align 3164 // property. 3165 void nsLineLayout::TextAlignLine(nsLineBox* aLine, bool aIsLastLine) { 3166 /** 3167 * NOTE: aIsLastLine ain't necessarily so: it is correctly set by caller 3168 * only in cases where the last line needs special handling. 3169 */ 3170 PerSpanData* psd = mRootSpan; 3171 WritingMode lineWM = psd->mWritingMode; 3172 LAYOUT_WARN_IF_FALSE(psd->mIEnd != NS_UNCONSTRAINEDSIZE, 3173 "have unconstrained width; this should only result from " 3174 "very large sizes, not attempts at intrinsic width " 3175 "calculation"); 3176 nscoord availISize = psd->mIEnd - psd->mIStart; 3177 nscoord remainingISize = availISize - aLine->ISize(); 3178 #ifdef NOISY_INLINEDIR_ALIGN 3179 LineContainerFrame()->ListTag(stdout); 3180 printf(": availISize=%d lineBounds.IStart=%d lineISize=%d delta=%d\n", 3181 availISize, aLine->IStart(), aLine->ISize(), remainingISize); 3182 #endif 3183 3184 nscoord dx = 0; 3185 StyleTextAlign textAlign = 3186 aIsLastLine ? mStyleText->TextAlignForLastLine() : mStyleText->mTextAlign; 3187 3188 // Check if there's trailing whitespace we need to "hang" at line-wrap. 3189 nscoord hang = 0; 3190 uint32_t trimCount = 0; 3191 if (aLine->IsLineWrapped()) { 3192 if (textAlign == StyleTextAlign::Justify) { 3193 auto trim = GetTrimFrom(mRootSpan, lineWM.IsBidiRTL()); 3194 hang = NSToCoordRound(trim.mAdvance); 3195 trimCount = trim.mCount; 3196 } else { 3197 hang = GetHangFrom(mRootSpan, lineWM.IsBidiRTL()); 3198 } 3199 } 3200 3201 bool isSVG = LineContainerFrame()->IsInSVGTextSubtree(); 3202 bool doTextAlign = remainingISize > 0 || hang != 0; 3203 3204 int32_t additionalGaps = 0; 3205 if (!isSVG && 3206 (mHasRuby || (doTextAlign && textAlign == StyleTextAlign::Justify))) { 3207 JustificationComputationState computeState; 3208 ComputeFrameJustification(psd, computeState); 3209 if (mHasRuby && computeState.mFirstParticipant) { 3210 PerFrameData* firstFrame = computeState.mFirstParticipant; 3211 if (firstFrame->mFrame->Style()->ShouldSuppressLineBreak()) { 3212 MOZ_ASSERT(!firstFrame->mJustificationAssignment.mGapsAtStart); 3213 nsIFrame* rubyBase = FindNearestRubyBaseAncestor(firstFrame->mFrame); 3214 if (rubyBase && IsRubyAlignSpaceAround(rubyBase)) { 3215 firstFrame->mJustificationAssignment.mGapsAtStart = 1; 3216 additionalGaps++; 3217 } 3218 } 3219 PerFrameData* lastFrame = computeState.mLastParticipant; 3220 if (lastFrame->mFrame->Style()->ShouldSuppressLineBreak()) { 3221 MOZ_ASSERT(!lastFrame->mJustificationAssignment.mGapsAtEnd); 3222 nsIFrame* rubyBase = FindNearestRubyBaseAncestor(lastFrame->mFrame); 3223 if (rubyBase && IsRubyAlignSpaceAround(rubyBase)) { 3224 lastFrame->mJustificationAssignment.mGapsAtEnd = 1; 3225 additionalGaps++; 3226 } 3227 } 3228 } 3229 } 3230 3231 if (!isSVG && doTextAlign) { 3232 switch (textAlign) { 3233 case StyleTextAlign::Justify: { 3234 int32_t opportunities = 3235 psd->mFrame->mJustificationInfo.mInnerOpportunities - 3236 (hang ? trimCount : 0); 3237 if (opportunities > 0) { 3238 int32_t gaps = opportunities * 2 + additionalGaps; 3239 remainingISize += std::abs(hang); 3240 JustificationApplicationState applyState(gaps, remainingISize); 3241 3242 // Apply the justification, and make sure to update our linebox 3243 // width to account for it. 3244 aLine->ExpandBy(ApplyFrameJustification(psd, applyState), 3245 ContainerSizeForSpan(psd)); 3246 3247 // If the trimmable trailing whitespace that we want to hang had 3248 // reverse-inline directionality, adjust line position to account for 3249 // it being at the inline-start side. 3250 // On top of the original "hang" amount, justification will have 3251 // modified its width, so we include that adjustment here. 3252 if (hang < 0) { 3253 dx = hang - trimCount * remainingISize / opportunities; 3254 } 3255 3256 // Gaps that belong to trimmed whitespace were not included in the 3257 // applyState count, so we need to add them here for the assert. 3258 DebugOnly<int32_t> trimmedGaps = hang ? trimCount * 2 : 0; 3259 MOZ_ASSERT(applyState.mGaps.mHandled == 3260 applyState.mGaps.mCount + trimmedGaps, 3261 "Unprocessed justification gaps"); 3262 // Similarly, account for the adjustment applied to the trimmed 3263 // whitespace, which is in addition to the adjustment that applies 3264 // within the actual width of the line. 3265 DebugOnly<int32_t> trimmedAdjustment = 3266 trimCount * remainingISize / opportunities; 3267 NS_ASSERTION(applyState.mWidth.mConsumed == 3268 applyState.mWidth.mAvailable + trimmedAdjustment, 3269 "Unprocessed justification width"); 3270 break; 3271 } 3272 // Fall through to the default case if we could not justify to fill 3273 // the space. 3274 [[fallthrough]]; 3275 } 3276 3277 case StyleTextAlign::Start: 3278 // Default alignment is to start edge so do nothing, except to apply 3279 // any "reverse-hang" amount resulting from reversed-direction trailing 3280 // space. 3281 // Char is for tables so treat as start if we find it in block layout. 3282 if (hang < 0) { 3283 dx = hang; 3284 } 3285 break; 3286 3287 case StyleTextAlign::Left: 3288 case StyleTextAlign::MozLeft: 3289 if (lineWM.IsBidiRTL()) { 3290 dx = remainingISize + (hang > 0 ? hang : 0); 3291 } else if (hang < 0) { 3292 dx = hang; 3293 } 3294 break; 3295 3296 case StyleTextAlign::Right: 3297 case StyleTextAlign::MozRight: 3298 if (lineWM.IsBidiLTR()) { 3299 dx = remainingISize + (hang > 0 ? hang : 0); 3300 } else if (hang < 0) { 3301 dx = hang; 3302 } 3303 break; 3304 3305 case StyleTextAlign::End: 3306 dx = remainingISize + (hang > 0 ? hang : 0); 3307 break; 3308 3309 case StyleTextAlign::Center: 3310 case StyleTextAlign::MozCenter: 3311 dx = (remainingISize + hang) / 2; 3312 break; 3313 } 3314 } 3315 3316 if (mHasRuby) { 3317 ExpandInlineRubyBoxes(mRootSpan); 3318 } 3319 3320 PerFrameData* startFrame = psd->mFirstFrame; 3321 MOZ_ASSERT(startFrame, "empty line?"); 3322 if (startFrame->mIsMarker) { 3323 // ::marker shouldn't participate in bidi reordering nor text alignment. 3324 startFrame = startFrame->mNext; 3325 MOZ_ASSERT(startFrame, "no frame after ::marker?"); 3326 MOZ_ASSERT(!startFrame->mIsMarker, "multiple ::markers?"); 3327 } 3328 3329 const bool bidi = mPresContext->BidiEnabled() && 3330 (!mPresContext->IsVisualMode() || lineWM.IsBidiRTL()); 3331 if (bidi) { 3332 nsBidiPresUtils::ReorderFrames(startFrame->mFrame, aLine->GetChildCount(), 3333 lineWM, mContainerSize, 3334 psd->mIStart + mTextIndent + dx); 3335 } 3336 3337 if (dx) { 3338 // For the bidi case, if startFrame is a ::first-line frame, the mIStart and 3339 // mTextIndent offsets will already have been applied to its position, but 3340 // we still need to apply the text-align adjustment |dx| to its position. 3341 const bool needToAdjustFrames = !bidi || startFrame->mFrame->IsLineFrame(); 3342 MOZ_ASSERT_IF(startFrame->mFrame->IsLineFrame(), !startFrame->mNext); 3343 if (needToAdjustFrames) { 3344 for (PerFrameData* pfd = startFrame; pfd; pfd = pfd->mNext) { 3345 pfd->mBounds.IStart(lineWM) += dx; 3346 pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd)); 3347 } 3348 } 3349 aLine->IndentBy(dx, ContainerSize()); 3350 } 3351 } 3352 3353 // This method applies any relative positioning to the given frame. 3354 void nsLineLayout::ApplyRelativePositioning(PerFrameData* aPFD) { 3355 if (!aPFD->mIsRelativelyOrStickyPos) { 3356 return; 3357 } 3358 3359 nsIFrame* frame = aPFD->mFrame; 3360 WritingMode frameWM = aPFD->mWritingMode; 3361 LogicalPoint origin = frame->GetLogicalPosition(ContainerSize()); 3362 // right and bottom are handled by 3363 // ReflowInput::ComputeRelativeOffsets 3364 ReflowInput::ApplyRelativePositioning(frame, frameWM, aPFD->mOffsets, &origin, 3365 ContainerSize()); 3366 frame->SetPosition(frameWM, origin, ContainerSize()); 3367 } 3368 3369 // This method do relative positioning for ruby annotations. 3370 void nsLineLayout::RelativePositionAnnotations(PerSpanData* aRubyPSD, 3371 OverflowAreas& aOverflowAreas) { 3372 MOZ_ASSERT(aRubyPSD->mFrame->mFrame->IsRubyFrame()); 3373 for (PerFrameData* pfd = aRubyPSD->mFirstFrame; pfd; pfd = pfd->mNext) { 3374 MOZ_ASSERT(pfd->mFrame->IsRubyBaseContainerFrame()); 3375 for (PerFrameData* rtc = pfd->mNextAnnotation; rtc; 3376 rtc = rtc->mNextAnnotation) { 3377 nsIFrame* rtcFrame = rtc->mFrame; 3378 MOZ_ASSERT(rtcFrame->IsRubyTextContainerFrame()); 3379 ApplyRelativePositioning(rtc); 3380 OverflowAreas rtcOverflowAreas; 3381 RelativePositionFrames(rtc->mSpan, rtcOverflowAreas); 3382 aOverflowAreas.UnionWith(rtcOverflowAreas + rtcFrame->GetPosition()); 3383 } 3384 } 3385 } 3386 3387 void nsLineLayout::RelativePositionFrames(PerSpanData* psd, 3388 OverflowAreas& aOverflowAreas) { 3389 OverflowAreas overflowAreas; 3390 WritingMode wm = psd->mWritingMode; 3391 if (psd != mRootSpan) { 3392 // The span's overflow areas come in three parts: 3393 // -- this frame's width and height 3394 // -- pfd->mOverflowAreas, which is the area of a ::marker or the union 3395 // of a relatively positioned frame's absolute children 3396 // -- the bounds of all inline descendants 3397 // The former two parts are computed right here, we gather the descendants 3398 // below. 3399 // At this point psd->mFrame->mBounds might be out of date since 3400 // bidi reordering can move and resize the frames. So use the frame's 3401 // rect instead of mBounds. 3402 nsRect adjustedBounds(nsPoint(0, 0), psd->mFrame->mFrame->GetSize()); 3403 3404 overflowAreas.ScrollableOverflow().UnionRect( 3405 psd->mFrame->mOverflowAreas.ScrollableOverflow(), adjustedBounds); 3406 overflowAreas.InkOverflow().UnionRect( 3407 psd->mFrame->mOverflowAreas.InkOverflow(), adjustedBounds); 3408 } else { 3409 // Note(dshin, bug 1940938): `mICoord` can be negative due to a negative 3410 // `text-indent` value (i.e. "Outdenting"). Ensure that we have a valid 3411 // overflow rect for that case. 3412 const auto iStart = std::min(psd->mIStart, psd->mICoord); 3413 const auto iSize = std::abs(psd->mICoord - psd->mIStart); 3414 LogicalRect rect(wm, iStart, mBStartEdge, iSize, mFinalLineBSize); 3415 // The minimum combined area for the frames that are direct 3416 // children of the block starts at the upper left corner of the 3417 // line and is sized to match the size of the line's bounding box 3418 // (the same size as the values returned from VerticalAlignFrames) 3419 overflowAreas.InkOverflow() = rect.GetPhysicalRect(wm, ContainerSize()); 3420 overflowAreas.ScrollableOverflow() = overflowAreas.InkOverflow(); 3421 } 3422 3423 for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) { 3424 nsIFrame* frame = pfd->mFrame; 3425 3426 // Adjust the origin of the frame 3427 ApplyRelativePositioning(pfd); 3428 3429 // Note: the combined area of a child is in its coordinate 3430 // system. We adjust the childs combined area into our coordinate 3431 // system before computing the aggregated value by adding in 3432 // <b>x</b> and <b>y</b> which were computed above. 3433 OverflowAreas r; 3434 if (pfd->mSpan) { 3435 // Compute a new combined area for the child span before 3436 // aggregating it into our combined area. 3437 RelativePositionFrames(pfd->mSpan, r); 3438 } else { 3439 r = pfd->mOverflowAreas; 3440 if (pfd->mIsTextFrame) { 3441 // We need to recompute overflow areas in four cases: 3442 // (1) When PFD_RECOMPUTEOVERFLOW is set due to trimming 3443 // (2) When there are text decorations, since we can't recompute the 3444 // overflow area until Reflow and VerticalAlignLine have finished 3445 // (3) When there are text emphasis marks, since the marks may be 3446 // put further away if the text is inside ruby. 3447 // (4) When there are text strokes 3448 if (pfd->mRecomputeOverflow || 3449 frame->Style()->HasTextDecorationLines() || 3450 frame->StyleText()->HasEffectiveTextEmphasis() || 3451 frame->StyleText()->HasWebkitTextStroke()) { 3452 nsTextFrame* f = static_cast<nsTextFrame*>(frame); 3453 r = f->RecomputeOverflow(LineContainerFrame()); 3454 } 3455 frame->FinishAndStoreOverflow(r, frame->GetSize()); 3456 } 3457 } 3458 3459 overflowAreas.UnionWith(r + frame->GetPosition()); 3460 } 3461 3462 // Also compute relative position in the annotations. 3463 if (psd->mFrame->mFrame->IsRubyFrame()) { 3464 RelativePositionAnnotations(psd, overflowAreas); 3465 } 3466 3467 // If we just computed a spans combined area, we need to update its 3468 // overflow rect... 3469 if (psd != mRootSpan) { 3470 PerFrameData* spanPFD = psd->mFrame; 3471 nsIFrame* frame = spanPFD->mFrame; 3472 frame->FinishAndStoreOverflow(overflowAreas, frame->GetSize()); 3473 } 3474 aOverflowAreas = overflowAreas; 3475 }