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/. */
7 /* rendering object for CSS "display: ruby-base-container" */
9 #include "nsRubyBaseContainerFrame.h"
10 #include "nsRubyTextContainerFrame.h"
11 #include "nsRubyBaseFrame.h"
12 #include "nsRubyTextFrame.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 "nsStyleStructInlines.h"
22 #include "nsTextFrame.h"
23 #include "gfxContext.h"
24 #include "RubyUtils.h"
26 using namespace mozilla
;
27 using namespace mozilla::gfx
;
29 //----------------------------------------------------------------------
31 // Frame class boilerplate
32 // =======================
34 NS_QUERYFRAME_HEAD(nsRubyBaseContainerFrame
)
35 NS_QUERYFRAME_ENTRY(nsRubyBaseContainerFrame
)
36 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
38 NS_IMPL_FRAMEARENA_HELPERS(nsRubyBaseContainerFrame
)
40 nsContainerFrame
* NS_NewRubyBaseContainerFrame(PresShell
* aPresShell
,
41 ComputedStyle
* aStyle
) {
42 return new (aPresShell
)
43 nsRubyBaseContainerFrame(aStyle
, aPresShell
->GetPresContext());
46 //----------------------------------------------------------------------
48 // nsRubyBaseContainerFrame Method Implementations
49 // ===============================================
51 #ifdef DEBUG_FRAME_DUMP
52 nsresult
nsRubyBaseContainerFrame::GetFrameName(nsAString
& aResult
) const {
53 return MakeFrameName(u
"RubyBaseContainer"_ns
, aResult
);
57 static gfxBreakPriority
LineBreakBefore(nsIFrame
* aFrame
,
58 DrawTarget
* aDrawTarget
,
59 nsIFrame
* aLineContainerFrame
,
60 const nsLineList::iterator
* aLine
) {
61 for (nsIFrame
* child
= aFrame
; child
;
62 child
= child
->PrincipalChildList().FirstChild()) {
63 if (!child
->CanContinueTextRun()) {
64 // It is not an inline element. We can break before it.
65 return gfxBreakPriority::eNormalBreak
;
67 if (!child
->IsTextFrame()) {
71 auto textFrame
= static_cast<nsTextFrame
*>(child
);
72 gfxSkipCharsIterator iter
= textFrame
->EnsureTextRun(
73 nsTextFrame::eInflated
, aDrawTarget
, aLineContainerFrame
, aLine
);
74 iter
.SetOriginalOffset(textFrame
->GetContentOffset());
75 uint32_t pos
= iter
.GetSkippedOffset();
76 gfxTextRun
* textRun
= textFrame
->GetTextRun(nsTextFrame::eInflated
);
77 MOZ_ASSERT(textRun
, "fail to build textrun?");
78 if (!textRun
|| pos
>= textRun
->GetLength()) {
79 // The text frame contains no character at all.
80 return gfxBreakPriority::eNoBreak
;
82 // Return whether we can break before the first character.
83 if (textRun
->CanBreakLineBefore(pos
)) {
84 return gfxBreakPriority::eNormalBreak
;
86 // Check whether we can wrap word here.
87 const nsStyleText
* textStyle
= textFrame
->StyleText();
88 if (textStyle
->WordCanWrap(textFrame
) && textRun
->IsClusterStart(pos
)) {
89 return gfxBreakPriority::eWordWrapBreak
;
91 // We cannot break before.
92 return gfxBreakPriority::eNoBreak
;
94 // Neither block, nor text frame is found as a leaf. We won't break
95 // before this base frame. It is the behavior of empty spans.
96 return gfxBreakPriority::eNoBreak
;
99 static void GetIsLineBreakAllowed(nsIFrame
* aFrame
, bool aIsLineBreakable
,
100 bool* aAllowInitialLineBreak
,
101 bool* aAllowLineBreak
) {
102 nsIFrame
* parent
= aFrame
->GetParent();
103 bool lineBreakSuppressed
= parent
->Style()->ShouldSuppressLineBreak();
104 // Allow line break between ruby bases when white-space allows,
105 // we are not inside a nested ruby, and there is no span.
106 bool allowLineBreak
=
107 !lineBreakSuppressed
&& aFrame
->StyleText()->WhiteSpaceCanWrap(aFrame
);
108 bool allowInitialLineBreak
= allowLineBreak
;
109 if (!aFrame
->GetPrevInFlow()) {
110 allowInitialLineBreak
=
111 !lineBreakSuppressed
&& parent
->StyleText()->WhiteSpaceCanWrap(parent
);
113 if (!aIsLineBreakable
) {
114 allowInitialLineBreak
= false;
116 *aAllowInitialLineBreak
= allowInitialLineBreak
;
117 *aAllowLineBreak
= allowLineBreak
;
121 * @param aBaseISizeData is an in/out param. This method updates the
122 * `skipWhitespace` and `trailingWhitespace` fields of the struct with
123 * the base level frame. Note that we don't need to do the same thing
124 * for ruby text frames, because they are text run container themselves
125 * (see nsTextFrame.cpp:BuildTextRuns), and thus no whitespace collapse
126 * happens across the boundary of those frames.
128 static nscoord
CalculateColumnPrefISize(
129 gfxContext
* aRenderingContext
, const RubyColumnEnumerator
& aEnumerator
,
130 nsIFrame::InlineIntrinsicISizeData
* aBaseISizeData
) {
132 uint32_t levelCount
= aEnumerator
.GetLevelCount();
133 for (uint32_t i
= 0; i
< levelCount
; i
++) {
134 nsIFrame
* frame
= aEnumerator
.GetFrameAtLevel(i
);
136 nsIFrame::InlinePrefISizeData data
;
138 data
.SetLineContainer(aBaseISizeData
->LineContainer());
139 data
.mSkipWhitespace
= aBaseISizeData
->mSkipWhitespace
;
140 data
.mTrailingWhitespace
= aBaseISizeData
->mTrailingWhitespace
;
142 // The line container of ruby text frames is their parent,
143 // ruby text container frame.
144 data
.SetLineContainer(frame
->GetParent());
146 frame
->AddInlinePrefISize(aRenderingContext
, &data
);
147 MOZ_ASSERT(data
.mPrevLines
== 0, "Shouldn't have prev lines");
148 max
= std::max(max
, data
.mCurrentLine
);
150 aBaseISizeData
->mSkipWhitespace
= data
.mSkipWhitespace
;
151 aBaseISizeData
->mTrailingWhitespace
= data
.mTrailingWhitespace
;
158 // FIXME Currently we use pref isize of ruby content frames for
159 // computing min isize of ruby frame, which may cause problem.
162 void nsRubyBaseContainerFrame::AddInlineMinISize(
163 gfxContext
* aRenderingContext
, nsIFrame::InlineMinISizeData
* aData
) {
164 AutoRubyTextContainerArray
textContainers(this);
166 for (uint32_t i
= 0, iend
= textContainers
.Length(); i
< iend
; i
++) {
167 if (textContainers
[i
]->IsSpanContainer()) {
168 // Since spans are not breakable internally, use our pref isize
169 // directly if there is any span.
170 nsIFrame::InlinePrefISizeData data
;
171 data
.SetLineContainer(aData
->LineContainer());
172 data
.mSkipWhitespace
= aData
->mSkipWhitespace
;
173 data
.mTrailingWhitespace
= aData
->mTrailingWhitespace
;
174 AddInlinePrefISize(aRenderingContext
, &data
);
175 aData
->mCurrentLine
+= data
.mCurrentLine
;
176 if (data
.mCurrentLine
> 0) {
177 aData
->mAtStartOfLine
= false;
179 aData
->mSkipWhitespace
= data
.mSkipWhitespace
;
180 aData
->mTrailingWhitespace
= data
.mTrailingWhitespace
;
185 bool firstFrame
= true;
186 bool allowInitialLineBreak
, allowLineBreak
;
187 GetIsLineBreakAllowed(this, !aData
->mAtStartOfLine
, &allowInitialLineBreak
,
189 for (nsIFrame
* frame
= this; frame
; frame
= frame
->GetNextInFlow()) {
190 RubyColumnEnumerator
enumerator(
191 static_cast<nsRubyBaseContainerFrame
*>(frame
), textContainers
);
192 for (; !enumerator
.AtEnd(); enumerator
.Next()) {
193 if (firstFrame
? allowInitialLineBreak
: allowLineBreak
) {
194 nsIFrame
* baseFrame
= enumerator
.GetFrameAtLevel(0);
196 gfxBreakPriority breakPriority
= LineBreakBefore(
197 baseFrame
, aRenderingContext
->GetDrawTarget(), nullptr, nullptr);
198 if (breakPriority
!= gfxBreakPriority::eNoBreak
) {
199 aData
->OptionallyBreak();
205 CalculateColumnPrefISize(aRenderingContext
, enumerator
, aData
);
206 aData
->mCurrentLine
+= isize
;
208 aData
->mAtStartOfLine
= false;
215 void nsRubyBaseContainerFrame::AddInlinePrefISize(
216 gfxContext
* aRenderingContext
, nsIFrame::InlinePrefISizeData
* aData
) {
217 AutoRubyTextContainerArray
textContainers(this);
220 for (nsIFrame
* frame
= this; frame
; frame
= frame
->GetNextInFlow()) {
221 RubyColumnEnumerator
enumerator(
222 static_cast<nsRubyBaseContainerFrame
*>(frame
), textContainers
);
223 for (; !enumerator
.AtEnd(); enumerator
.Next()) {
224 sum
+= CalculateColumnPrefISize(aRenderingContext
, enumerator
, aData
);
227 for (uint32_t i
= 0, iend
= textContainers
.Length(); i
< iend
; i
++) {
228 if (textContainers
[i
]->IsSpanContainer()) {
229 nsIFrame
* frame
= textContainers
[i
]->PrincipalChildList().FirstChild();
230 nsIFrame::InlinePrefISizeData data
;
231 frame
->AddInlinePrefISize(aRenderingContext
, &data
);
232 MOZ_ASSERT(data
.mPrevLines
== 0, "Shouldn't have prev lines");
233 sum
= std::max(sum
, data
.mCurrentLine
);
236 aData
->mCurrentLine
+= sum
;
240 bool nsRubyBaseContainerFrame::CanContinueTextRun() const { return true; }
243 nsIFrame::SizeComputationResult
nsRubyBaseContainerFrame::ComputeSize(
244 gfxContext
* aRenderingContext
, WritingMode aWM
, const LogicalSize
& aCBSize
,
245 nscoord aAvailableISize
, const LogicalSize
& aMargin
,
246 const LogicalSize
& aBorderPadding
, const StyleSizeOverrides
& aSizeOverrides
,
247 ComputeSizeFlags aFlags
) {
248 // Ruby base container frame is inline,
249 // hence don't compute size before reflow.
250 return {LogicalSize(aWM
, NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
),
251 AspectRatioUsage::None
};
254 Maybe
<nscoord
> nsRubyBaseContainerFrame::GetNaturalBaselineBOffset(
255 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
,
256 BaselineExportContext
) const {
257 if (aBaselineGroup
== BaselineSharingGroup::Last
) {
260 return Some(mBaseline
);
263 struct nsRubyBaseContainerFrame::RubyReflowInput
{
264 bool mAllowInitialLineBreak
;
265 bool mAllowLineBreak
;
266 const AutoRubyTextContainerArray
& mTextContainers
;
267 const ReflowInput
& mBaseReflowInput
;
268 const nsTArray
<UniquePtr
<ReflowInput
>>& mTextReflowInputs
;
272 void nsRubyBaseContainerFrame::Reflow(nsPresContext
* aPresContext
,
273 ReflowOutput
& aDesiredSize
,
274 const ReflowInput
& aReflowInput
,
275 nsReflowStatus
& aStatus
) {
277 DO_GLOBAL_REFLOW_COUNT("nsRubyBaseContainerFrame");
278 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aDesiredSize
, aStatus
);
279 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
281 if (!aReflowInput
.mLineLayout
) {
283 aReflowInput
.mLineLayout
,
284 "No line layout provided to RubyBaseContainerFrame reflow method.");
288 mDescendantLeadings
.Reset();
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
);
299 WritingMode lineWM
= aReflowInput
.mLineLayout
->GetWritingMode();
300 LogicalSize
availSize(lineWM
, aReflowInput
.AvailableISize(),
301 aReflowInput
.AvailableBSize());
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
);
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()) {
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
);
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
;
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
,
348 lineLayout
->AttachRootFrameToBaseLineLayout();
351 aReflowInput
.mLineLayout
->BeginSpan(
352 this, &aReflowInput
, 0, aReflowInput
.AvailableISize(), &mBaseline
);
354 bool allowInitialLineBreak
, allowLineBreak
;
355 GetIsLineBreakAllowed(this, aReflowInput
.mLineLayout
->LineIsBreakable(),
356 &allowInitialLineBreak
, &allowLineBreak
);
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(),
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
) {
380 RubyReflowInput reflowInput
= {false, false, textContainers
, aReflowInput
,
382 nscoord spanISize
= ReflowSpans(reflowInput
);
383 isize
= std::max(isize
, spanISize
);
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();
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
398 if (!textContainer
->IsSpanContainer()) {
400 } else if (isize
> rtcISize
) {
401 RubyUtils::SetReservedISize(textContainer
, isize
- rtcISize
);
404 lineLayout
->VerticalAlignLine();
405 textContainer
->SetISize(rtcISize
);
406 lineLayout
->EndLineReflow();
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
,
420 ReflowAbsoluteFrames(aPresContext
, aDesiredSize
, aReflowInput
, aStatus
);
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.
428 struct MOZ_STACK_CLASS
nsRubyBaseContainerFrame::PullFrameState
{
429 ContinuationTraversingState mBase
;
430 AutoTArray
<ContinuationTraversingState
, RTC_ARRAY_SIZE
> mTexts
;
431 const AutoRubyTextContainerArray
& mTextContainers
;
433 PullFrameState(nsRubyBaseContainerFrame
* aBaseContainer
,
434 const AutoRubyTextContainerArray
& aTextContainers
);
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
;
447 uint32_t columnIndex
= 0;
449 column
.mTextFrames
.SetCapacity(rtcCount
);
450 RubyColumnEnumerator
e(this, aReflowInput
.mTextContainers
);
451 for (; !e
.AtEnd(); e
.Next()) {
453 icoord
+= ReflowOneColumn(aReflowInput
, columnIndex
, column
, aDesiredSize
,
455 if (!reflowStatus
.IsInlineBreakBefore()) {
458 if (reflowStatus
.IsInlineBreak()) {
461 // We are not handling overflow here.
462 MOZ_ASSERT(reflowStatus
.IsEmpty());
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());
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
);
475 // No more frames can be pulled.
478 icoord
+= ReflowOneColumn(aReflowInput
, columnIndex
, column
, aDesiredSize
,
480 if (!reflowStatus
.IsInlineBreakBefore()) {
485 if (!e
.AtEnd() && reflowStatus
.IsInlineBreakAfter()) {
486 // The current column has been successfully placed.
487 // Skip to the next column and mark break before.
490 reflowStatus
.SetInlineLineBreakBeforeAndReset();
492 if (!e
.AtEnd() || (GetNextInFlow() && !isComplete
)) {
493 aStatus
.SetIncomplete();
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();
503 aStatus
.SetInlineLineBreakAfter();
504 MOZ_ASSERT(aStatus
.IsComplete() || aReflowInput
.mAllowLineBreak
);
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()) {
515 nextColumn
.emplace();
516 e
.GetColumn(nextColumn
.ref());
518 nsIFrame
* baseFrame
= column
.mBaseFrame
;
519 if (!baseFrame
& nextColumn
.isSome()) {
520 baseFrame
= nextColumn
->mBaseFrame
;
523 PushChildrenToOverflow(baseFrame
, baseFrame
->GetPrevSibling());
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
];
531 aReflowInput
.mTextContainers
[i
]->PushChildrenToOverflow(
532 textFrame
, textFrame
->GetPrevSibling());
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();
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();
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();
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;
586 nsAutoString baseText
;
587 if (aColumn
.mBaseFrame
) {
588 nsLayoutUtils::GetFrameTextContent(aColumn
.mBaseFrame
, baseText
);
591 // Reflow text frames
592 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
593 nsRubyTextFrame
* textFrame
= aColumn
.mTextFrames
[i
];
595 bool isCollapsed
= false;
596 if (textFrame
->StyleVisibility()->mVisible
== StyleVisibility::Collapse
) {
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
);
609 textFrame
->AddStateBits(NS_RUBY_TEXT_FRAME_COLLAPSED
);
611 textFrame
->RemoveStateBits(NS_RUBY_TEXT_FRAME_COLLAPSED
);
613 RubyUtils::ClearReservedISize(textFrame
);
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();
627 nscoord textISize
= lineLayout
->GetCurrentICoord() - textIStart
;
628 columnISize
= std::max(columnISize
, textISize
);
632 // Reflow the base frame
633 if (aColumn
.mBaseFrame
) {
634 RubyUtils::ClearReservedISize(aColumn
.mBaseFrame
);
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
,
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();
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
;
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
);
675 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
676 if (aReflowInput
.mTextContainers
[i
]->IsSpanContainer()) {
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
);
688 if (aColumn
.mBaseFrame
&& textFrame
) {
689 lineLayout
->AttachLastFrameToBaseLineLayout();
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
]);
706 void nsRubyBaseContainerFrame::PullOneColumn(nsLineLayout
* aLineLayout
,
707 PullFrameState
& aPullFrameState
,
710 const AutoRubyTextContainerArray
& textContainers
=
711 aPullFrameState
.mTextContainers
;
712 const uint32_t rtcCount
= textContainers
.Length();
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();
721 aColumn
.mTextFrames
.ClearAndRetainStorage();
722 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
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();
733 // If there exists any frame in continations, we haven't
734 // completed the reflow process.
735 aIsComplete
= !foundFrame
;
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;
748 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
749 nsRubyTextFrame
*& textFrame
= aColumn
.mTextFrames
[i
];
750 if (textFrame
&& !textFrame
->IsIntraLevelWhitespace()) {
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());
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");
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);
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?");
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?");
793 // We pulled frames from the next line, hence mark it dirty.
794 aLineLayout
->SetDirtyNextLine();
798 nscoord
nsRubyBaseContainerFrame::ReflowSpans(
799 const RubyReflowInput
& aReflowInput
) {
800 nscoord spanISize
= 0;
801 for (uint32_t i
= 0, iend
= aReflowInput
.mTextContainers
.Length(); i
< iend
;
803 nsRubyTextContainerFrame
* container
= aReflowInput
.mTextContainers
[i
];
804 if (!container
->IsSpanContainer()) {
808 nsIFrame
* rtFrame
= container
->PrincipalChildList().FirstChild();
809 nsReflowStatus reflowStatus
;
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());