nsRubyBaseContainerFrame.cpp (33247B)
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 /* rendering object for CSS "display: ruby-base-container" */ 8 9 #include "nsRubyBaseContainerFrame.h" 10 11 #include "RubyUtils.h" 12 #include "gfxContext.h" 13 #include "mozilla/ComputedStyle.h" 14 #include "mozilla/DebugOnly.h" 15 #include "mozilla/Maybe.h" 16 #include "mozilla/PresShell.h" 17 #include "mozilla/WritingModes.h" 18 #include "nsLayoutUtils.h" 19 #include "nsLineLayout.h" 20 #include "nsPresContext.h" 21 #include "nsRubyBaseFrame.h" 22 #include "nsRubyTextContainerFrame.h" 23 #include "nsRubyTextFrame.h" 24 #include "nsStyleStructInlines.h" 25 #include "nsTextFrame.h" 26 27 using namespace mozilla; 28 using namespace mozilla::gfx; 29 30 //---------------------------------------------------------------------- 31 32 // Frame class boilerplate 33 // ======================= 34 35 NS_QUERYFRAME_HEAD(nsRubyBaseContainerFrame) 36 NS_QUERYFRAME_ENTRY(nsRubyBaseContainerFrame) 37 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 38 39 NS_IMPL_FRAMEARENA_HELPERS(nsRubyBaseContainerFrame) 40 41 nsContainerFrame* NS_NewRubyBaseContainerFrame(PresShell* aPresShell, 42 ComputedStyle* aStyle) { 43 return new (aPresShell) 44 nsRubyBaseContainerFrame(aStyle, aPresShell->GetPresContext()); 45 } 46 47 //---------------------------------------------------------------------- 48 49 // nsRubyBaseContainerFrame Method Implementations 50 // =============================================== 51 52 #ifdef DEBUG_FRAME_DUMP 53 nsresult nsRubyBaseContainerFrame::GetFrameName(nsAString& aResult) const { 54 return MakeFrameName(u"RubyBaseContainer"_ns, aResult); 55 } 56 #endif 57 58 static gfxBreakPriority LineBreakBefore(nsIFrame* aFrame, 59 DrawTarget* aDrawTarget, 60 nsIFrame* aLineContainerFrame, 61 const nsLineList::iterator* aLine) { 62 for (nsIFrame* child = aFrame; child; 63 child = child->PrincipalChildList().FirstChild()) { 64 if (!child->CanContinueTextRun()) { 65 // It is not an inline element. We can break before it. 66 return gfxBreakPriority::eNormalBreak; 67 } 68 if (!child->IsTextFrame()) { 69 continue; 70 } 71 72 auto textFrame = static_cast<nsTextFrame*>(child); 73 gfxSkipCharsIterator iter = textFrame->EnsureTextRun( 74 nsTextFrame::eInflated, aDrawTarget, aLineContainerFrame, aLine); 75 iter.SetOriginalOffset(textFrame->GetContentOffset()); 76 uint32_t pos = iter.GetSkippedOffset(); 77 gfxTextRun* textRun = textFrame->GetTextRun(nsTextFrame::eInflated); 78 MOZ_ASSERT(textRun, "fail to build textrun?"); 79 if (!textRun || pos >= textRun->GetLength()) { 80 // The text frame contains no character at all. 81 return gfxBreakPriority::eNoBreak; 82 } 83 // Return whether we can break before the first character. 84 if (textRun->CanBreakLineBefore(pos)) { 85 return gfxBreakPriority::eNormalBreak; 86 } 87 // Check whether we can wrap word here. 88 const nsStyleText* textStyle = textFrame->StyleText(); 89 if (textStyle->WordCanWrap(textFrame) && textRun->IsClusterStart(pos)) { 90 return gfxBreakPriority::eWordWrapBreak; 91 } 92 // We cannot break before. 93 return gfxBreakPriority::eNoBreak; 94 } 95 // Neither block, nor text frame is found as a leaf. We won't break 96 // before this base frame. It is the behavior of empty spans. 97 return gfxBreakPriority::eNoBreak; 98 } 99 100 static void GetIsLineBreakAllowed(nsIFrame* aFrame, bool aIsLineBreakable, 101 bool* aAllowInitialLineBreak, 102 bool* aAllowLineBreak) { 103 nsIFrame* parent = aFrame->GetParent(); 104 bool lineBreakSuppressed = parent->Style()->ShouldSuppressLineBreak(); 105 // Allow line break between ruby bases when white-space allows, 106 // we are not inside a nested ruby, and there is no span. 107 bool allowLineBreak = 108 !lineBreakSuppressed && aFrame->StyleText()->WhiteSpaceCanWrap(aFrame); 109 bool allowInitialLineBreak = allowLineBreak; 110 if (!aFrame->GetPrevInFlow()) { 111 allowInitialLineBreak = 112 !lineBreakSuppressed && parent->StyleText()->WhiteSpaceCanWrap(parent); 113 } 114 if (!aIsLineBreakable) { 115 allowInitialLineBreak = false; 116 } 117 *aAllowInitialLineBreak = allowInitialLineBreak; 118 *aAllowLineBreak = allowLineBreak; 119 } 120 121 /** 122 * @param aBaseISizeData is an in/out param. This method updates the 123 * `skipWhitespace` and `trailingWhitespace` fields of the struct with 124 * the base level frame. Note that we don't need to do the same thing 125 * for ruby text frames, because they are text run container themselves 126 * (see nsTextFrame.cpp:BuildTextRuns), and thus no whitespace collapse 127 * happens across the boundary of those frames. 128 */ 129 static nscoord CalculateColumnPrefISize( 130 const IntrinsicSizeInput& aInput, const RubyColumnEnumerator& aEnumerator, 131 nsIFrame::InlineIntrinsicISizeData* aBaseISizeData) { 132 nscoord max = 0; 133 uint32_t levelCount = aEnumerator.GetLevelCount(); 134 for (uint32_t i = 0; i < levelCount; i++) { 135 nsIFrame* frame = aEnumerator.GetFrameAtLevel(i); 136 if (frame) { 137 nsIFrame::InlinePrefISizeData data; 138 if (i == 0) { 139 data.SetLineContainer(aBaseISizeData->LineContainer()); 140 data.mSkipWhitespace = aBaseISizeData->mSkipWhitespace; 141 data.mTrailingWhitespace = aBaseISizeData->mTrailingWhitespace; 142 } else { 143 // The line container of ruby text frames is their parent, 144 // ruby text container frame. 145 data.SetLineContainer(frame->GetParent()); 146 } 147 frame->AddInlinePrefISize(aInput, &data); 148 MOZ_ASSERT(data.mPrevLines == 0, "Shouldn't have prev lines"); 149 max = std::max(max, data.mCurrentLine); 150 if (i == 0) { 151 aBaseISizeData->mSkipWhitespace = data.mSkipWhitespace; 152 aBaseISizeData->mTrailingWhitespace = data.mTrailingWhitespace; 153 } 154 } 155 } 156 return max; 157 } 158 159 // FIXME Currently we use pref isize of ruby content frames for 160 // computing min isize of ruby frame, which may cause problem. 161 // See bug 1134945. 162 /* virtual */ 163 void nsRubyBaseContainerFrame::AddInlineMinISize( 164 const IntrinsicSizeInput& aInput, InlineMinISizeData* aData) { 165 AutoRubyTextContainerArray textContainers(this); 166 167 for (uint32_t i = 0, iend = textContainers.Length(); i < iend; i++) { 168 if (textContainers[i]->IsSpanContainer()) { 169 // Since spans are not breakable internally, use our pref isize 170 // directly if there is any span. 171 InlinePrefISizeData data; 172 data.SetLineContainer(aData->LineContainer()); 173 data.mSkipWhitespace = aData->mSkipWhitespace; 174 data.mTrailingWhitespace = aData->mTrailingWhitespace; 175 AddInlinePrefISize(aInput, &data); 176 aData->mCurrentLine += data.mCurrentLine; 177 if (data.mCurrentLine > 0) { 178 aData->mAtStartOfLine = false; 179 } 180 aData->mSkipWhitespace = data.mSkipWhitespace; 181 aData->mTrailingWhitespace = data.mTrailingWhitespace; 182 return; 183 } 184 } 185 186 bool firstFrame = true; 187 bool allowInitialLineBreak, allowLineBreak; 188 GetIsLineBreakAllowed(this, !aData->mAtStartOfLine, &allowInitialLineBreak, 189 &allowLineBreak); 190 for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) { 191 RubyColumnEnumerator enumerator( 192 static_cast<nsRubyBaseContainerFrame*>(frame), textContainers); 193 for (; !enumerator.AtEnd(); enumerator.Next()) { 194 if (firstFrame ? allowInitialLineBreak : allowLineBreak) { 195 nsIFrame* baseFrame = enumerator.GetFrameAtLevel(0); 196 if (baseFrame) { 197 gfxBreakPriority breakPriority = LineBreakBefore( 198 baseFrame, aInput.mContext->GetDrawTarget(), nullptr, nullptr); 199 if (breakPriority != gfxBreakPriority::eNoBreak) { 200 aData->OptionallyBreak(); 201 } 202 } 203 } 204 firstFrame = false; 205 nscoord isize = CalculateColumnPrefISize(aInput, enumerator, aData); 206 aData->mCurrentLine += isize; 207 if (isize > 0) { 208 aData->mAtStartOfLine = false; 209 } 210 } 211 } 212 } 213 214 /* virtual */ 215 void nsRubyBaseContainerFrame::AddInlinePrefISize( 216 const IntrinsicSizeInput& aInput, InlinePrefISizeData* aData) { 217 AutoRubyTextContainerArray textContainers(this); 218 const IntrinsicSizeInput input(aInput.mContext, Nothing(), Nothing()); 219 220 nscoord sum = 0; 221 for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) { 222 RubyColumnEnumerator enumerator( 223 static_cast<nsRubyBaseContainerFrame*>(frame), textContainers); 224 for (; !enumerator.AtEnd(); enumerator.Next()) { 225 sum += CalculateColumnPrefISize(input, enumerator, aData); 226 } 227 } 228 for (uint32_t i = 0, iend = textContainers.Length(); i < iend; i++) { 229 if (textContainers[i]->IsSpanContainer()) { 230 nsIFrame* frame = textContainers[i]->PrincipalChildList().FirstChild(); 231 InlinePrefISizeData data; 232 frame->AddInlinePrefISize(input, &data); 233 MOZ_ASSERT(data.mPrevLines == 0, "Shouldn't have prev lines"); 234 sum = std::max(sum, data.mCurrentLine); 235 } 236 } 237 aData->mCurrentLine += sum; 238 } 239 240 /* virtual */ 241 bool nsRubyBaseContainerFrame::CanContinueTextRun() const { return true; } 242 243 /* virtual */ 244 nsIFrame::SizeComputationResult nsRubyBaseContainerFrame::ComputeSize( 245 const SizeComputationInput& aSizingInput, WritingMode aWM, 246 const LogicalSize& aCBSize, nscoord aAvailableISize, 247 const LogicalSize& aMargin, const LogicalSize& aBorderPadding, 248 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 249 // Ruby base container frame is inline, 250 // hence don't compute size before reflow. 251 return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), 252 AspectRatioUsage::None}; 253 } 254 255 Maybe<nscoord> nsRubyBaseContainerFrame::GetNaturalBaselineBOffset( 256 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 257 BaselineExportContext) const { 258 if (aBaselineGroup == BaselineSharingGroup::Last) { 259 return Nothing{}; 260 } 261 return Some(mBaseline); 262 } 263 264 struct nsRubyBaseContainerFrame::RubyReflowInput { 265 bool mAllowInitialLineBreak; 266 bool mAllowLineBreak; 267 const AutoRubyTextContainerArray& mTextContainers; 268 const ReflowInput& mBaseReflowInput; 269 const nsTArray<UniquePtr<ReflowInput>>& mTextReflowInputs; 270 }; 271 272 /* virtual */ 273 void nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext, 274 ReflowOutput& aDesiredSize, 275 const ReflowInput& aReflowInput, 276 nsReflowStatus& aStatus) { 277 MarkInReflow(); 278 DO_GLOBAL_REFLOW_COUNT("nsRubyBaseContainerFrame"); 279 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 280 281 if (!aReflowInput.mLineLayout) { 282 NS_ASSERTION( 283 aReflowInput.mLineLayout, 284 "No line layout provided to RubyBaseContainerFrame reflow method."); 285 return; 286 } 287 288 mDescendantLeadings.Reset(); 289 290 nsIFrame* lineContainer = aReflowInput.mLineLayout->LineContainerFrame(); 291 MoveInlineOverflowToChildList(lineContainer); 292 // Ask text containers to drain overflows 293 AutoRubyTextContainerArray textContainers(this); 294 const uint32_t rtcCount = textContainers.Length(); 295 for (uint32_t i = 0; i < rtcCount; i++) { 296 textContainers[i]->MoveInlineOverflowToChildList(lineContainer); 297 } 298 299 WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode(); 300 LogicalSize availSize(lineWM, aReflowInput.AvailableISize(), 301 aReflowInput.AvailableBSize()); 302 303 // We have a reflow input and a line layout for each RTC. 304 // They are conceptually the state of the RTCs, but we don't actually 305 // reflow those RTCs in this code. These two arrays are holders of 306 // the reflow inputs and line layouts. 307 // Since there are pointers refer to reflow inputs and line layouts, 308 // it is necessary to guarantee that they won't be moved. For this 309 // reason, they are wrapped in UniquePtr here. 310 AutoTArray<UniquePtr<ReflowInput>, RTC_ARRAY_SIZE> reflowInputs; 311 AutoTArray<UniquePtr<nsLineLayout>, RTC_ARRAY_SIZE> lineLayouts; 312 reflowInputs.SetCapacity(rtcCount); 313 lineLayouts.SetCapacity(rtcCount); 314 315 // Begin the line layout for each ruby text container in advance. 316 bool hasSpan = false; 317 for (uint32_t i = 0; i < rtcCount; i++) { 318 nsRubyTextContainerFrame* textContainer = textContainers[i]; 319 WritingMode rtcWM = textContainer->GetWritingMode(); 320 WritingMode reflowWM = lineWM.IsOrthogonalTo(rtcWM) ? rtcWM : lineWM; 321 if (textContainer->IsSpanContainer()) { 322 hasSpan = true; 323 } 324 325 ReflowInput* reflowInput = new ReflowInput( 326 aPresContext, *aReflowInput.mParentReflowInput, textContainer, 327 availSize.ConvertTo(textContainer->GetWritingMode(), lineWM)); 328 reflowInputs.AppendElement(reflowInput); 329 nsLineLayout* lineLayout = 330 new nsLineLayout(aPresContext, reflowInput->mFloatManager, *reflowInput, 331 nullptr, aReflowInput.mLineLayout); 332 lineLayout->SetSuppressLineWrap(true); 333 lineLayouts.AppendElement(lineLayout); 334 335 // Line number is useless for ruby text 336 // XXX nullptr here may cause problem, see comments for 337 // nsLineLayout::mBlockRI and nsLineLayout::AddFloat 338 lineLayout->Init(nullptr, reflowInput->GetLineHeight(), -1); 339 reflowInput->mLineLayout = lineLayout; 340 341 // Border and padding are suppressed on ruby text containers. 342 // If the writing mode is vertical-rl, the horizontal position of 343 // rt frames will be updated when reflowing this text container, 344 // hence leave container size 0 here for now. 345 lineLayout->BeginLineReflow(0, 0, reflowInput->ComputedISize(), 346 NS_UNCONSTRAINEDSIZE, false, false, reflowWM, 347 nsSize(0, 0)); 348 lineLayout->AttachRootFrameToBaseLineLayout(); 349 } 350 351 aReflowInput.mLineLayout->BeginSpan( 352 this, &aReflowInput, 0, aReflowInput.AvailableISize(), &mBaseline); 353 354 bool allowInitialLineBreak, allowLineBreak; 355 GetIsLineBreakAllowed(this, aReflowInput.mLineLayout->LineIsBreakable(), 356 &allowInitialLineBreak, &allowLineBreak); 357 358 // Reflow columns excluding any span 359 RubyReflowInput reflowInput = {allowInitialLineBreak, 360 allowLineBreak && !hasSpan, textContainers, 361 aReflowInput, reflowInputs}; 362 aDesiredSize.BSize(lineWM) = 0; 363 aDesiredSize.SetBlockStartAscent(0); 364 nscoord isize = ReflowColumns(reflowInput, aDesiredSize, aStatus); 365 DebugOnly<nscoord> lineSpanSize = aReflowInput.mLineLayout->EndSpan(this); 366 aDesiredSize.ISize(lineWM) = isize; 367 // When there are no frames inside the ruby base container, EndSpan 368 // will return 0. However, in this case, the actual width of the 369 // container could be non-zero because of non-empty ruby annotations. 370 // XXX When bug 765861 gets fixed, this warning should be upgraded. 371 NS_WARNING_ASSERTION( 372 aStatus.IsInlineBreak() || isize == lineSpanSize || mFrames.IsEmpty(), 373 "bad isize"); 374 375 // If there exists any span, the columns must either be completely 376 // reflowed, or be not reflowed at all. 377 MOZ_ASSERT(aStatus.IsInlineBreakBefore() || aStatus.IsComplete() || !hasSpan); 378 if (!aStatus.IsInlineBreakBefore() && aStatus.IsComplete() && hasSpan) { 379 // Reflow spans 380 RubyReflowInput reflowInput = {false, false, textContainers, aReflowInput, 381 reflowInputs}; 382 nscoord spanISize = ReflowSpans(reflowInput); 383 isize = std::max(isize, spanISize); 384 } 385 386 for (uint32_t i = 0; i < rtcCount; i++) { 387 // It happens before the ruby text container is reflowed, and that 388 // when it is reflowed, it will just use this size. 389 nsRubyTextContainerFrame* textContainer = textContainers[i]; 390 nsLineLayout* lineLayout = lineLayouts[i].get(); 391 392 RubyUtils::ClearReservedISize(textContainer); 393 nscoord rtcISize = lineLayout->GetCurrentICoord(); 394 // Only span containers and containers with collapsed annotations 395 // need reserving isize. For normal ruby text containers, their 396 // children will be expanded properly. We only need to expand their 397 // own size. 398 if (!textContainer->IsSpanContainer()) { 399 rtcISize = isize; 400 } else if (isize > rtcISize) { 401 RubyUtils::SetReservedISize(textContainer, isize - rtcISize); 402 } 403 404 lineLayout->VerticalAlignLine(); 405 textContainer->SetISize(rtcISize); 406 lineLayout->EndLineReflow(); 407 } 408 409 // If this ruby base container is empty, size it as if there were 410 // an empty inline child inside. 411 if (mFrames.IsEmpty()) { 412 // Border and padding are suppressed on ruby base container, so we 413 // create a dummy zero-sized borderPadding for setting BSize. 414 WritingMode frameWM = aReflowInput.GetWritingMode(); 415 LogicalMargin borderPadding(frameWM); 416 nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, borderPadding, 417 lineWM, frameWM); 418 } 419 420 ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus); 421 } 422 423 /** 424 * This struct stores the continuations after this frame and 425 * corresponding text containers. It is used to speed up looking 426 * ahead for nonempty continuations. 427 */ 428 struct MOZ_STACK_CLASS nsRubyBaseContainerFrame::PullFrameState { 429 ContinuationTraversingState mBase; 430 AutoTArray<ContinuationTraversingState, RTC_ARRAY_SIZE> mTexts; 431 const AutoRubyTextContainerArray& mTextContainers; 432 433 PullFrameState(nsRubyBaseContainerFrame* aBaseContainer, 434 const AutoRubyTextContainerArray& aTextContainers); 435 }; 436 437 nscoord nsRubyBaseContainerFrame::ReflowColumns( 438 const RubyReflowInput& aReflowInput, ReflowOutput& aDesiredSize, 439 nsReflowStatus& aStatus) { 440 nsLineLayout* lineLayout = aReflowInput.mBaseReflowInput.mLineLayout; 441 const uint32_t rtcCount = aReflowInput.mTextContainers.Length(); 442 nscoord icoord = lineLayout->GetCurrentICoord(); 443 MOZ_ASSERT(icoord == 0, "border/padding of rbc should have been suppressed"); 444 nsReflowStatus reflowStatus; 445 aStatus.Reset(); 446 447 uint32_t columnIndex = 0; 448 RubyColumn column; 449 column.mTextFrames.SetCapacity(rtcCount); 450 RubyColumnEnumerator e(this, aReflowInput.mTextContainers); 451 for (; !e.AtEnd(); e.Next()) { 452 e.GetColumn(column); 453 icoord += ReflowOneColumn(aReflowInput, columnIndex, column, aDesiredSize, 454 reflowStatus); 455 if (!reflowStatus.IsInlineBreakBefore()) { 456 columnIndex++; 457 } 458 if (reflowStatus.IsInlineBreak()) { 459 break; 460 } 461 // We are not handling overflow here. 462 MOZ_ASSERT(reflowStatus.IsEmpty()); 463 } 464 465 bool isComplete = false; 466 PullFrameState pullFrameState(this, aReflowInput.mTextContainers); 467 while (!reflowStatus.IsInlineBreak()) { 468 // We are not handling overflow here. 469 MOZ_ASSERT(reflowStatus.IsEmpty()); 470 471 // Try pull some frames from next continuations. This call replaces 472 // frames in |column| with the frame pulled in each level. 473 PullOneColumn(lineLayout, pullFrameState, column, isComplete); 474 if (isComplete) { 475 // No more frames can be pulled. 476 break; 477 } 478 icoord += ReflowOneColumn(aReflowInput, columnIndex, column, aDesiredSize, 479 reflowStatus); 480 if (!reflowStatus.IsInlineBreakBefore()) { 481 columnIndex++; 482 } 483 } 484 485 if (!e.AtEnd() && reflowStatus.IsInlineBreakAfter()) { 486 // The current column has been successfully placed. 487 // Skip to the next column and mark break before. 488 e.Next(); 489 e.GetColumn(column); 490 reflowStatus.SetInlineLineBreakBeforeAndReset(); 491 } 492 if (!e.AtEnd() || (GetNextInFlow() && !isComplete)) { 493 aStatus.SetIncomplete(); 494 } 495 496 if (reflowStatus.IsInlineBreakBefore()) { 497 if (!columnIndex || !aReflowInput.mAllowLineBreak) { 498 // If no column has been placed yet, or we have any span, 499 // the whole container should be in the next line. 500 aStatus.SetInlineLineBreakBeforeAndReset(); 501 return 0; 502 } 503 aStatus.SetInlineLineBreakAfter(); 504 MOZ_ASSERT(aStatus.IsComplete() || aReflowInput.mAllowLineBreak); 505 506 // If we are on an intra-level whitespace column, null values in 507 // column.mBaseFrame and column.mTextFrames don't represent the 508 // end of the frame-sibling-chain at that level -- instead, they 509 // represent an anonymous empty intra-level whitespace box. It is 510 // likely that there are frames in the next column (which can't be 511 // intra-level whitespace). Those frames should be pushed as well. 512 Maybe<RubyColumn> nextColumn; 513 if (column.mIsIntraLevelWhitespace && !e.AtEnd()) { 514 e.Next(); 515 nextColumn.emplace(); 516 e.GetColumn(nextColumn.ref()); 517 } 518 nsIFrame* baseFrame = column.mBaseFrame; 519 if (!baseFrame & nextColumn.isSome()) { 520 baseFrame = nextColumn->mBaseFrame; 521 } 522 if (baseFrame) { 523 PushChildrenToOverflow(baseFrame, baseFrame->GetPrevSibling()); 524 } 525 for (uint32_t i = 0; i < rtcCount; i++) { 526 nsRubyTextFrame* textFrame = column.mTextFrames[i]; 527 if (!textFrame && nextColumn.isSome()) { 528 textFrame = nextColumn->mTextFrames[i]; 529 } 530 if (textFrame) { 531 aReflowInput.mTextContainers[i]->PushChildrenToOverflow( 532 textFrame, textFrame->GetPrevSibling()); 533 } 534 } 535 } else if (reflowStatus.IsInlineBreakAfter()) { 536 // |reflowStatus| being break after here may only happen when 537 // there is a break after the column just pulled, or the whole 538 // segment has been completely reflowed. In those cases, we do 539 // not need to push anything. 540 MOZ_ASSERT(e.AtEnd()); 541 aStatus.SetInlineLineBreakAfter(); 542 } 543 544 return icoord; 545 } 546 547 nscoord nsRubyBaseContainerFrame::ReflowOneColumn( 548 const RubyReflowInput& aReflowInput, uint32_t aColumnIndex, 549 const RubyColumn& aColumn, ReflowOutput& aDesiredSize, 550 nsReflowStatus& aStatus) { 551 const ReflowInput& baseReflowInput = aReflowInput.mBaseReflowInput; 552 const auto& textReflowInputs = aReflowInput.mTextReflowInputs; 553 nscoord istart = baseReflowInput.mLineLayout->GetCurrentICoord(); 554 555 if (aColumn.mBaseFrame) { 556 bool allowBreakBefore = aColumnIndex ? aReflowInput.mAllowLineBreak 557 : aReflowInput.mAllowInitialLineBreak; 558 if (allowBreakBefore) { 559 gfxBreakPriority breakPriority = 560 LineBreakBefore(aColumn.mBaseFrame, 561 baseReflowInput.mRenderingContext->GetDrawTarget(), 562 baseReflowInput.mLineLayout->LineContainerFrame(), 563 baseReflowInput.mLineLayout->GetLine()); 564 if (breakPriority != gfxBreakPriority::eNoBreak) { 565 gfxBreakPriority lastBreakPriority = 566 baseReflowInput.mLineLayout->LastOptionalBreakPriority(); 567 if (breakPriority >= lastBreakPriority) { 568 // Either we have been overflow, or we are forced 569 // to break here, do break before. 570 if (istart > baseReflowInput.AvailableISize() || 571 baseReflowInput.mLineLayout->NotifyOptionalBreakPosition( 572 aColumn.mBaseFrame, 0, true, breakPriority)) { 573 aStatus.SetInlineLineBreakBeforeAndReset(); 574 return 0; 575 } 576 } 577 } 578 } 579 } 580 581 const uint32_t rtcCount = aReflowInput.mTextContainers.Length(); 582 MOZ_ASSERT(aColumn.mTextFrames.Length() == rtcCount); 583 MOZ_ASSERT(textReflowInputs.Length() == rtcCount); 584 nscoord columnISize = 0; 585 586 nsAutoString baseText; 587 if (aColumn.mBaseFrame) { 588 nsLayoutUtils::GetFrameTextContent(aColumn.mBaseFrame, baseText); 589 } 590 591 // Reflow text frames 592 for (uint32_t i = 0; i < rtcCount; i++) { 593 nsRubyTextFrame* textFrame = aColumn.mTextFrames[i]; 594 if (textFrame) { 595 bool isCollapsed = false; 596 if (textFrame->StyleVisibility()->mVisible == StyleVisibility::Collapse) { 597 isCollapsed = true; 598 } else { 599 // Per CSS Ruby spec, the content comparison for auto-hiding 600 // takes place prior to white spaces collapsing (white-space) 601 // and text transformation (text-transform), and ignores elements 602 // (considers only the textContent of the boxes). Which means 603 // using the content tree text comparison is correct. 604 nsAutoString annotationText; 605 nsLayoutUtils::GetFrameTextContent(textFrame, annotationText); 606 isCollapsed = annotationText.Equals(baseText); 607 } 608 if (isCollapsed) { 609 textFrame->AddStateBits(NS_RUBY_TEXT_FRAME_COLLAPSED); 610 } else { 611 textFrame->RemoveStateBits(NS_RUBY_TEXT_FRAME_COLLAPSED); 612 } 613 RubyUtils::ClearReservedISize(textFrame); 614 615 bool pushedFrame; 616 nsReflowStatus reflowStatus; 617 nsLineLayout* lineLayout = textReflowInputs[i]->mLineLayout; 618 nscoord textIStart = lineLayout->GetCurrentICoord(); 619 lineLayout->ReflowFrame(textFrame, reflowStatus, nullptr, pushedFrame); 620 if (MOZ_UNLIKELY(reflowStatus.IsInlineBreak() || pushedFrame)) { 621 MOZ_ASSERT_UNREACHABLE( 622 "Any line break inside ruby box should have been suppressed"); 623 // For safety, always drain the overflow list, so that 624 // no frames are left there after reflow. 625 textFrame->DrainSelfOverflowList(); 626 } 627 nscoord textISize = lineLayout->GetCurrentICoord() - textIStart; 628 columnISize = std::max(columnISize, textISize); 629 } 630 } 631 632 // Reflow the base frame 633 if (aColumn.mBaseFrame) { 634 RubyUtils::ClearReservedISize(aColumn.mBaseFrame); 635 636 bool pushedFrame; 637 nsReflowStatus reflowStatus; 638 nsLineLayout* lineLayout = baseReflowInput.mLineLayout; 639 WritingMode lineWM = lineLayout->GetWritingMode(); 640 nscoord baseIStart = lineLayout->GetCurrentICoord(); 641 ReflowOutput metrics(lineWM); 642 lineLayout->ReflowFrame(aColumn.mBaseFrame, reflowStatus, &metrics, 643 pushedFrame); 644 if (MOZ_UNLIKELY(reflowStatus.IsInlineBreak() || pushedFrame)) { 645 MOZ_ASSERT_UNREACHABLE( 646 "Any line break inside ruby box should have been suppressed"); 647 // For safety, always drain the overflow list, so that 648 // no frames are left there after reflow. 649 aColumn.mBaseFrame->DrainSelfOverflowList(); 650 } 651 nscoord baseISize = lineLayout->GetCurrentICoord() - baseIStart; 652 columnISize = std::max(columnISize, baseISize); 653 // Calculate ascent & descent of the base frame and update desired 654 // size of this base container accordingly. 655 nscoord oldAscent = aDesiredSize.BlockStartAscent(); 656 nscoord oldDescent = aDesiredSize.BSize(lineWM) - oldAscent; 657 nscoord baseAscent = metrics.BlockStartAscent(); 658 nscoord baseDesent = metrics.BSize(lineWM) - baseAscent; 659 LogicalMargin margin = aColumn.mBaseFrame->GetLogicalUsedMargin(lineWM); 660 nscoord newAscent = std::max(baseAscent + margin.BStart(lineWM), oldAscent); 661 nscoord newDescent = std::max(baseDesent + margin.BEnd(lineWM), oldDescent); 662 aDesiredSize.SetBlockStartAscent(newAscent); 663 aDesiredSize.BSize(lineWM) = newAscent + newDescent; 664 } 665 666 // Align all the line layout to the new coordinate. 667 nscoord icoord = istart + columnISize; 668 nscoord deltaISize = icoord - baseReflowInput.mLineLayout->GetCurrentICoord(); 669 if (deltaISize > 0) { 670 baseReflowInput.mLineLayout->AdvanceICoord(deltaISize); 671 if (aColumn.mBaseFrame) { 672 RubyUtils::SetReservedISize(aColumn.mBaseFrame, deltaISize); 673 } 674 } 675 for (uint32_t i = 0; i < rtcCount; i++) { 676 if (aReflowInput.mTextContainers[i]->IsSpanContainer()) { 677 continue; 678 } 679 nsLineLayout* lineLayout = textReflowInputs[i]->mLineLayout; 680 nsRubyTextFrame* textFrame = aColumn.mTextFrames[i]; 681 nscoord deltaISize = icoord - lineLayout->GetCurrentICoord(); 682 if (deltaISize > 0) { 683 lineLayout->AdvanceICoord(deltaISize); 684 if (textFrame && !textFrame->IsCollapsed()) { 685 RubyUtils::SetReservedISize(textFrame, deltaISize); 686 } 687 } 688 if (aColumn.mBaseFrame && textFrame) { 689 lineLayout->AttachLastFrameToBaseLineLayout(); 690 } 691 } 692 693 return columnISize; 694 } 695 696 nsRubyBaseContainerFrame::PullFrameState::PullFrameState( 697 nsRubyBaseContainerFrame* aBaseContainer, 698 const AutoRubyTextContainerArray& aTextContainers) 699 : mBase(aBaseContainer), mTextContainers(aTextContainers) { 700 const uint32_t rtcCount = aTextContainers.Length(); 701 for (uint32_t i = 0; i < rtcCount; i++) { 702 mTexts.AppendElement(aTextContainers[i]); 703 } 704 } 705 706 void nsRubyBaseContainerFrame::PullOneColumn(nsLineLayout* aLineLayout, 707 PullFrameState& aPullFrameState, 708 RubyColumn& aColumn, 709 bool& aIsComplete) { 710 const AutoRubyTextContainerArray& textContainers = 711 aPullFrameState.mTextContainers; 712 const uint32_t rtcCount = textContainers.Length(); 713 714 nsIFrame* nextBase = GetNextInFlowChild(aPullFrameState.mBase); 715 MOZ_ASSERT(!nextBase || nextBase->IsRubyBaseFrame()); 716 aColumn.mBaseFrame = static_cast<nsRubyBaseFrame*>(nextBase); 717 bool foundFrame = !!aColumn.mBaseFrame; 718 bool pullingIntraLevelWhitespace = 719 aColumn.mBaseFrame && aColumn.mBaseFrame->IsIntraLevelWhitespace(); 720 721 aColumn.mTextFrames.ClearAndRetainStorage(); 722 for (uint32_t i = 0; i < rtcCount; i++) { 723 nsIFrame* nextText = 724 textContainers[i]->GetNextInFlowChild(aPullFrameState.mTexts[i]); 725 MOZ_ASSERT(!nextText || nextText->IsRubyTextFrame()); 726 nsRubyTextFrame* textFrame = static_cast<nsRubyTextFrame*>(nextText); 727 aColumn.mTextFrames.AppendElement(textFrame); 728 foundFrame = foundFrame || nextText; 729 if (nextText && !pullingIntraLevelWhitespace) { 730 pullingIntraLevelWhitespace = textFrame->IsIntraLevelWhitespace(); 731 } 732 } 733 // If there exists any frame in continations, we haven't 734 // completed the reflow process. 735 aIsComplete = !foundFrame; 736 if (!foundFrame) { 737 return; 738 } 739 740 aColumn.mIsIntraLevelWhitespace = pullingIntraLevelWhitespace; 741 if (pullingIntraLevelWhitespace) { 742 // We are pulling an intra-level whitespace. Drop all frames which 743 // are not part of this intra-level whitespace column. (Those frames 744 // are really part of the *next* column, after the pulled one.) 745 if (aColumn.mBaseFrame && !aColumn.mBaseFrame->IsIntraLevelWhitespace()) { 746 aColumn.mBaseFrame = nullptr; 747 } 748 for (uint32_t i = 0; i < rtcCount; i++) { 749 nsRubyTextFrame*& textFrame = aColumn.mTextFrames[i]; 750 if (textFrame && !textFrame->IsIntraLevelWhitespace()) { 751 textFrame = nullptr; 752 } 753 } 754 } else { 755 // We are not pulling an intra-level whitespace, which means all 756 // elements we are going to pull can have non-whitespace content, 757 // which may contain float which we need to reparent. 758 MOZ_ASSERT(aColumn.begin() != aColumn.end(), 759 "Ruby column shouldn't be empty"); 760 nsBlockFrame* oldFloatCB = 761 nsLayoutUtils::GetFloatContainingBlock(*aColumn.begin()); 762 #ifdef DEBUG 763 MOZ_ASSERT(oldFloatCB, "Must have found a float containing block"); 764 for (nsIFrame* frame : aColumn) { 765 MOZ_ASSERT(nsLayoutUtils::GetFloatContainingBlock(frame) == oldFloatCB, 766 "All frames in the same ruby column should share " 767 "the same old float containing block"); 768 } 769 #endif 770 nsBlockFrame* newFloatCB = do_QueryFrame(aLineLayout->LineContainerFrame()); 771 MOZ_ASSERT(newFloatCB, "Must have a float containing block"); 772 if (oldFloatCB != newFloatCB) { 773 for (nsIFrame* frame : aColumn) { 774 newFloatCB->ReparentFloats(frame, oldFloatCB, false); 775 } 776 } 777 } 778 779 // Pull the frames of this column. 780 if (aColumn.mBaseFrame) { 781 DebugOnly<nsIFrame*> pulled = PullNextInFlowChild(aPullFrameState.mBase); 782 MOZ_ASSERT(pulled == aColumn.mBaseFrame, "pulled a wrong frame?"); 783 } 784 for (uint32_t i = 0; i < rtcCount; i++) { 785 if (aColumn.mTextFrames[i]) { 786 DebugOnly<nsIFrame*> pulled = 787 textContainers[i]->PullNextInFlowChild(aPullFrameState.mTexts[i]); 788 MOZ_ASSERT(pulled == aColumn.mTextFrames[i], "pulled a wrong frame?"); 789 } 790 } 791 792 if (!aIsComplete) { 793 // We pulled frames from the next line, hence mark it dirty. 794 aLineLayout->SetDirtyNextLine(); 795 } 796 } 797 798 nscoord nsRubyBaseContainerFrame::ReflowSpans( 799 const RubyReflowInput& aReflowInput) { 800 nscoord spanISize = 0; 801 for (uint32_t i = 0, iend = aReflowInput.mTextContainers.Length(); i < iend; 802 i++) { 803 nsRubyTextContainerFrame* container = aReflowInput.mTextContainers[i]; 804 if (!container->IsSpanContainer()) { 805 continue; 806 } 807 808 nsIFrame* rtFrame = container->PrincipalChildList().FirstChild(); 809 nsReflowStatus reflowStatus; 810 bool pushedFrame; 811 nsLineLayout* lineLayout = aReflowInput.mTextReflowInputs[i]->mLineLayout; 812 MOZ_ASSERT(lineLayout->GetCurrentICoord() == 0, 813 "border/padding of rtc should have been suppressed"); 814 lineLayout->ReflowFrame(rtFrame, reflowStatus, nullptr, pushedFrame); 815 MOZ_ASSERT(!reflowStatus.IsInlineBreak() && !pushedFrame, 816 "Any line break inside ruby box should has been suppressed"); 817 spanISize = std::max(spanISize, lineLayout->GetCurrentICoord()); 818 } 819 return spanISize; 820 }