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 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
280 if (!aReflowInput
.mLineLayout
) {
282 aReflowInput
.mLineLayout
,
283 "No line layout provided to RubyBaseContainerFrame reflow method.");
287 mDescendantLeadings
.Reset();
289 nsIFrame
* lineContainer
= aReflowInput
.mLineLayout
->LineContainerFrame();
290 MoveInlineOverflowToChildList(lineContainer
);
291 // Ask text containers to drain overflows
292 AutoRubyTextContainerArray
textContainers(this);
293 const uint32_t rtcCount
= textContainers
.Length();
294 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
295 textContainers
[i
]->MoveInlineOverflowToChildList(lineContainer
);
298 WritingMode lineWM
= aReflowInput
.mLineLayout
->GetWritingMode();
299 LogicalSize
availSize(lineWM
, aReflowInput
.AvailableISize(),
300 aReflowInput
.AvailableBSize());
302 // We have a reflow input and a line layout for each RTC.
303 // They are conceptually the state of the RTCs, but we don't actually
304 // reflow those RTCs in this code. These two arrays are holders of
305 // the reflow inputs and line layouts.
306 // Since there are pointers refer to reflow inputs and line layouts,
307 // it is necessary to guarantee that they won't be moved. For this
308 // reason, they are wrapped in UniquePtr here.
309 AutoTArray
<UniquePtr
<ReflowInput
>, RTC_ARRAY_SIZE
> reflowInputs
;
310 AutoTArray
<UniquePtr
<nsLineLayout
>, RTC_ARRAY_SIZE
> lineLayouts
;
311 reflowInputs
.SetCapacity(rtcCount
);
312 lineLayouts
.SetCapacity(rtcCount
);
314 // Begin the line layout for each ruby text container in advance.
315 bool hasSpan
= false;
316 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
317 nsRubyTextContainerFrame
* textContainer
= textContainers
[i
];
318 WritingMode rtcWM
= textContainer
->GetWritingMode();
319 WritingMode reflowWM
= lineWM
.IsOrthogonalTo(rtcWM
) ? rtcWM
: lineWM
;
320 if (textContainer
->IsSpanContainer()) {
324 ReflowInput
* reflowInput
= new ReflowInput(
325 aPresContext
, *aReflowInput
.mParentReflowInput
, textContainer
,
326 availSize
.ConvertTo(textContainer
->GetWritingMode(), lineWM
));
327 reflowInputs
.AppendElement(reflowInput
);
328 nsLineLayout
* lineLayout
=
329 new nsLineLayout(aPresContext
, reflowInput
->mFloatManager
, *reflowInput
,
330 nullptr, aReflowInput
.mLineLayout
);
331 lineLayout
->SetSuppressLineWrap(true);
332 lineLayouts
.AppendElement(lineLayout
);
334 // Line number is useless for ruby text
335 // XXX nullptr here may cause problem, see comments for
336 // nsLineLayout::mBlockRI and nsLineLayout::AddFloat
337 lineLayout
->Init(nullptr, reflowInput
->GetLineHeight(), -1);
338 reflowInput
->mLineLayout
= lineLayout
;
340 // Border and padding are suppressed on ruby text containers.
341 // If the writing mode is vertical-rl, the horizontal position of
342 // rt frames will be updated when reflowing this text container,
343 // hence leave container size 0 here for now.
344 lineLayout
->BeginLineReflow(0, 0, reflowInput
->ComputedISize(),
345 NS_UNCONSTRAINEDSIZE
, false, false, reflowWM
,
347 lineLayout
->AttachRootFrameToBaseLineLayout();
350 aReflowInput
.mLineLayout
->BeginSpan(
351 this, &aReflowInput
, 0, aReflowInput
.AvailableISize(), &mBaseline
);
353 bool allowInitialLineBreak
, allowLineBreak
;
354 GetIsLineBreakAllowed(this, aReflowInput
.mLineLayout
->LineIsBreakable(),
355 &allowInitialLineBreak
, &allowLineBreak
);
357 // Reflow columns excluding any span
358 RubyReflowInput reflowInput
= {allowInitialLineBreak
,
359 allowLineBreak
&& !hasSpan
, textContainers
,
360 aReflowInput
, reflowInputs
};
361 aDesiredSize
.BSize(lineWM
) = 0;
362 aDesiredSize
.SetBlockStartAscent(0);
363 nscoord isize
= ReflowColumns(reflowInput
, aDesiredSize
, aStatus
);
364 DebugOnly
<nscoord
> lineSpanSize
= aReflowInput
.mLineLayout
->EndSpan(this);
365 aDesiredSize
.ISize(lineWM
) = isize
;
366 // When there are no frames inside the ruby base container, EndSpan
367 // will return 0. However, in this case, the actual width of the
368 // container could be non-zero because of non-empty ruby annotations.
369 // XXX When bug 765861 gets fixed, this warning should be upgraded.
370 NS_WARNING_ASSERTION(
371 aStatus
.IsInlineBreak() || isize
== lineSpanSize
|| mFrames
.IsEmpty(),
374 // If there exists any span, the columns must either be completely
375 // reflowed, or be not reflowed at all.
376 MOZ_ASSERT(aStatus
.IsInlineBreakBefore() || aStatus
.IsComplete() || !hasSpan
);
377 if (!aStatus
.IsInlineBreakBefore() && aStatus
.IsComplete() && hasSpan
) {
379 RubyReflowInput reflowInput
= {false, false, textContainers
, aReflowInput
,
381 nscoord spanISize
= ReflowSpans(reflowInput
);
382 isize
= std::max(isize
, spanISize
);
385 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
386 // It happens before the ruby text container is reflowed, and that
387 // when it is reflowed, it will just use this size.
388 nsRubyTextContainerFrame
* textContainer
= textContainers
[i
];
389 nsLineLayout
* lineLayout
= lineLayouts
[i
].get();
391 RubyUtils::ClearReservedISize(textContainer
);
392 nscoord rtcISize
= lineLayout
->GetCurrentICoord();
393 // Only span containers and containers with collapsed annotations
394 // need reserving isize. For normal ruby text containers, their
395 // children will be expanded properly. We only need to expand their
397 if (!textContainer
->IsSpanContainer()) {
399 } else if (isize
> rtcISize
) {
400 RubyUtils::SetReservedISize(textContainer
, isize
- rtcISize
);
403 lineLayout
->VerticalAlignLine();
404 textContainer
->SetISize(rtcISize
);
405 lineLayout
->EndLineReflow();
408 // If this ruby base container is empty, size it as if there were
409 // an empty inline child inside.
410 if (mFrames
.IsEmpty()) {
411 // Border and padding are suppressed on ruby base container, so we
412 // create a dummy zero-sized borderPadding for setting BSize.
413 WritingMode frameWM
= aReflowInput
.GetWritingMode();
414 LogicalMargin
borderPadding(frameWM
);
415 nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize
, borderPadding
,
419 ReflowAbsoluteFrames(aPresContext
, aDesiredSize
, aReflowInput
, aStatus
);
423 * This struct stores the continuations after this frame and
424 * corresponding text containers. It is used to speed up looking
425 * ahead for nonempty continuations.
427 struct MOZ_STACK_CLASS
nsRubyBaseContainerFrame::PullFrameState
{
428 ContinuationTraversingState mBase
;
429 AutoTArray
<ContinuationTraversingState
, RTC_ARRAY_SIZE
> mTexts
;
430 const AutoRubyTextContainerArray
& mTextContainers
;
432 PullFrameState(nsRubyBaseContainerFrame
* aBaseContainer
,
433 const AutoRubyTextContainerArray
& aTextContainers
);
436 nscoord
nsRubyBaseContainerFrame::ReflowColumns(
437 const RubyReflowInput
& aReflowInput
, ReflowOutput
& aDesiredSize
,
438 nsReflowStatus
& aStatus
) {
439 nsLineLayout
* lineLayout
= aReflowInput
.mBaseReflowInput
.mLineLayout
;
440 const uint32_t rtcCount
= aReflowInput
.mTextContainers
.Length();
441 nscoord icoord
= lineLayout
->GetCurrentICoord();
442 MOZ_ASSERT(icoord
== 0, "border/padding of rbc should have been suppressed");
443 nsReflowStatus reflowStatus
;
446 uint32_t columnIndex
= 0;
448 column
.mTextFrames
.SetCapacity(rtcCount
);
449 RubyColumnEnumerator
e(this, aReflowInput
.mTextContainers
);
450 for (; !e
.AtEnd(); e
.Next()) {
452 icoord
+= ReflowOneColumn(aReflowInput
, columnIndex
, column
, aDesiredSize
,
454 if (!reflowStatus
.IsInlineBreakBefore()) {
457 if (reflowStatus
.IsInlineBreak()) {
460 // We are not handling overflow here.
461 MOZ_ASSERT(reflowStatus
.IsEmpty());
464 bool isComplete
= false;
465 PullFrameState
pullFrameState(this, aReflowInput
.mTextContainers
);
466 while (!reflowStatus
.IsInlineBreak()) {
467 // We are not handling overflow here.
468 MOZ_ASSERT(reflowStatus
.IsEmpty());
470 // Try pull some frames from next continuations. This call replaces
471 // frames in |column| with the frame pulled in each level.
472 PullOneColumn(lineLayout
, pullFrameState
, column
, isComplete
);
474 // No more frames can be pulled.
477 icoord
+= ReflowOneColumn(aReflowInput
, columnIndex
, column
, aDesiredSize
,
479 if (!reflowStatus
.IsInlineBreakBefore()) {
484 if (!e
.AtEnd() && reflowStatus
.IsInlineBreakAfter()) {
485 // The current column has been successfully placed.
486 // Skip to the next column and mark break before.
489 reflowStatus
.SetInlineLineBreakBeforeAndReset();
491 if (!e
.AtEnd() || (GetNextInFlow() && !isComplete
)) {
492 aStatus
.SetIncomplete();
495 if (reflowStatus
.IsInlineBreakBefore()) {
496 if (!columnIndex
|| !aReflowInput
.mAllowLineBreak
) {
497 // If no column has been placed yet, or we have any span,
498 // the whole container should be in the next line.
499 aStatus
.SetInlineLineBreakBeforeAndReset();
502 aStatus
.SetInlineLineBreakAfter();
503 MOZ_ASSERT(aStatus
.IsComplete() || aReflowInput
.mAllowLineBreak
);
505 // If we are on an intra-level whitespace column, null values in
506 // column.mBaseFrame and column.mTextFrames don't represent the
507 // end of the frame-sibling-chain at that level -- instead, they
508 // represent an anonymous empty intra-level whitespace box. It is
509 // likely that there are frames in the next column (which can't be
510 // intra-level whitespace). Those frames should be pushed as well.
511 Maybe
<RubyColumn
> nextColumn
;
512 if (column
.mIsIntraLevelWhitespace
&& !e
.AtEnd()) {
514 nextColumn
.emplace();
515 e
.GetColumn(nextColumn
.ref());
517 nsIFrame
* baseFrame
= column
.mBaseFrame
;
518 if (!baseFrame
& nextColumn
.isSome()) {
519 baseFrame
= nextColumn
->mBaseFrame
;
522 PushChildrenToOverflow(baseFrame
, baseFrame
->GetPrevSibling());
524 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
525 nsRubyTextFrame
* textFrame
= column
.mTextFrames
[i
];
526 if (!textFrame
&& nextColumn
.isSome()) {
527 textFrame
= nextColumn
->mTextFrames
[i
];
530 aReflowInput
.mTextContainers
[i
]->PushChildrenToOverflow(
531 textFrame
, textFrame
->GetPrevSibling());
534 } else if (reflowStatus
.IsInlineBreakAfter()) {
535 // |reflowStatus| being break after here may only happen when
536 // there is a break after the column just pulled, or the whole
537 // segment has been completely reflowed. In those cases, we do
538 // not need to push anything.
539 MOZ_ASSERT(e
.AtEnd());
540 aStatus
.SetInlineLineBreakAfter();
546 nscoord
nsRubyBaseContainerFrame::ReflowOneColumn(
547 const RubyReflowInput
& aReflowInput
, uint32_t aColumnIndex
,
548 const RubyColumn
& aColumn
, ReflowOutput
& aDesiredSize
,
549 nsReflowStatus
& aStatus
) {
550 const ReflowInput
& baseReflowInput
= aReflowInput
.mBaseReflowInput
;
551 const auto& textReflowInputs
= aReflowInput
.mTextReflowInputs
;
552 nscoord istart
= baseReflowInput
.mLineLayout
->GetCurrentICoord();
554 if (aColumn
.mBaseFrame
) {
555 bool allowBreakBefore
= aColumnIndex
? aReflowInput
.mAllowLineBreak
556 : aReflowInput
.mAllowInitialLineBreak
;
557 if (allowBreakBefore
) {
558 gfxBreakPriority breakPriority
=
559 LineBreakBefore(aColumn
.mBaseFrame
,
560 baseReflowInput
.mRenderingContext
->GetDrawTarget(),
561 baseReflowInput
.mLineLayout
->LineContainerFrame(),
562 baseReflowInput
.mLineLayout
->GetLine());
563 if (breakPriority
!= gfxBreakPriority::eNoBreak
) {
564 gfxBreakPriority lastBreakPriority
=
565 baseReflowInput
.mLineLayout
->LastOptionalBreakPriority();
566 if (breakPriority
>= lastBreakPriority
) {
567 // Either we have been overflow, or we are forced
568 // to break here, do break before.
569 if (istart
> baseReflowInput
.AvailableISize() ||
570 baseReflowInput
.mLineLayout
->NotifyOptionalBreakPosition(
571 aColumn
.mBaseFrame
, 0, true, breakPriority
)) {
572 aStatus
.SetInlineLineBreakBeforeAndReset();
580 const uint32_t rtcCount
= aReflowInput
.mTextContainers
.Length();
581 MOZ_ASSERT(aColumn
.mTextFrames
.Length() == rtcCount
);
582 MOZ_ASSERT(textReflowInputs
.Length() == rtcCount
);
583 nscoord columnISize
= 0;
585 nsAutoString baseText
;
586 if (aColumn
.mBaseFrame
) {
587 nsLayoutUtils::GetFrameTextContent(aColumn
.mBaseFrame
, baseText
);
590 // Reflow text frames
591 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
592 nsRubyTextFrame
* textFrame
= aColumn
.mTextFrames
[i
];
594 bool isCollapsed
= false;
595 if (textFrame
->StyleVisibility()->mVisible
== StyleVisibility::Collapse
) {
598 // Per CSS Ruby spec, the content comparison for auto-hiding
599 // takes place prior to white spaces collapsing (white-space)
600 // and text transformation (text-transform), and ignores elements
601 // (considers only the textContent of the boxes). Which means
602 // using the content tree text comparison is correct.
603 nsAutoString annotationText
;
604 nsLayoutUtils::GetFrameTextContent(textFrame
, annotationText
);
605 isCollapsed
= annotationText
.Equals(baseText
);
608 textFrame
->AddStateBits(NS_RUBY_TEXT_FRAME_COLLAPSED
);
610 textFrame
->RemoveStateBits(NS_RUBY_TEXT_FRAME_COLLAPSED
);
612 RubyUtils::ClearReservedISize(textFrame
);
615 nsReflowStatus reflowStatus
;
616 nsLineLayout
* lineLayout
= textReflowInputs
[i
]->mLineLayout
;
617 nscoord textIStart
= lineLayout
->GetCurrentICoord();
618 lineLayout
->ReflowFrame(textFrame
, reflowStatus
, nullptr, pushedFrame
);
619 if (MOZ_UNLIKELY(reflowStatus
.IsInlineBreak() || pushedFrame
)) {
620 MOZ_ASSERT_UNREACHABLE(
621 "Any line break inside ruby box should have been suppressed");
622 // For safety, always drain the overflow list, so that
623 // no frames are left there after reflow.
624 textFrame
->DrainSelfOverflowList();
626 nscoord textISize
= lineLayout
->GetCurrentICoord() - textIStart
;
627 columnISize
= std::max(columnISize
, textISize
);
631 // Reflow the base frame
632 if (aColumn
.mBaseFrame
) {
633 RubyUtils::ClearReservedISize(aColumn
.mBaseFrame
);
636 nsReflowStatus reflowStatus
;
637 nsLineLayout
* lineLayout
= baseReflowInput
.mLineLayout
;
638 WritingMode lineWM
= lineLayout
->GetWritingMode();
639 nscoord baseIStart
= lineLayout
->GetCurrentICoord();
640 ReflowOutput
metrics(lineWM
);
641 lineLayout
->ReflowFrame(aColumn
.mBaseFrame
, reflowStatus
, &metrics
,
643 if (MOZ_UNLIKELY(reflowStatus
.IsInlineBreak() || pushedFrame
)) {
644 MOZ_ASSERT_UNREACHABLE(
645 "Any line break inside ruby box should have been suppressed");
646 // For safety, always drain the overflow list, so that
647 // no frames are left there after reflow.
648 aColumn
.mBaseFrame
->DrainSelfOverflowList();
650 nscoord baseISize
= lineLayout
->GetCurrentICoord() - baseIStart
;
651 columnISize
= std::max(columnISize
, baseISize
);
652 // Calculate ascent & descent of the base frame and update desired
653 // size of this base container accordingly.
654 nscoord oldAscent
= aDesiredSize
.BlockStartAscent();
655 nscoord oldDescent
= aDesiredSize
.BSize(lineWM
) - oldAscent
;
656 nscoord baseAscent
= metrics
.BlockStartAscent();
657 nscoord baseDesent
= metrics
.BSize(lineWM
) - baseAscent
;
658 LogicalMargin margin
= aColumn
.mBaseFrame
->GetLogicalUsedMargin(lineWM
);
659 nscoord newAscent
= std::max(baseAscent
+ margin
.BStart(lineWM
), oldAscent
);
660 nscoord newDescent
= std::max(baseDesent
+ margin
.BEnd(lineWM
), oldDescent
);
661 aDesiredSize
.SetBlockStartAscent(newAscent
);
662 aDesiredSize
.BSize(lineWM
) = newAscent
+ newDescent
;
665 // Align all the line layout to the new coordinate.
666 nscoord icoord
= istart
+ columnISize
;
667 nscoord deltaISize
= icoord
- baseReflowInput
.mLineLayout
->GetCurrentICoord();
668 if (deltaISize
> 0) {
669 baseReflowInput
.mLineLayout
->AdvanceICoord(deltaISize
);
670 if (aColumn
.mBaseFrame
) {
671 RubyUtils::SetReservedISize(aColumn
.mBaseFrame
, deltaISize
);
674 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
675 if (aReflowInput
.mTextContainers
[i
]->IsSpanContainer()) {
678 nsLineLayout
* lineLayout
= textReflowInputs
[i
]->mLineLayout
;
679 nsRubyTextFrame
* textFrame
= aColumn
.mTextFrames
[i
];
680 nscoord deltaISize
= icoord
- lineLayout
->GetCurrentICoord();
681 if (deltaISize
> 0) {
682 lineLayout
->AdvanceICoord(deltaISize
);
683 if (textFrame
&& !textFrame
->IsCollapsed()) {
684 RubyUtils::SetReservedISize(textFrame
, deltaISize
);
687 if (aColumn
.mBaseFrame
&& textFrame
) {
688 lineLayout
->AttachLastFrameToBaseLineLayout();
695 nsRubyBaseContainerFrame::PullFrameState::PullFrameState(
696 nsRubyBaseContainerFrame
* aBaseContainer
,
697 const AutoRubyTextContainerArray
& aTextContainers
)
698 : mBase(aBaseContainer
), mTextContainers(aTextContainers
) {
699 const uint32_t rtcCount
= aTextContainers
.Length();
700 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
701 mTexts
.AppendElement(aTextContainers
[i
]);
705 void nsRubyBaseContainerFrame::PullOneColumn(nsLineLayout
* aLineLayout
,
706 PullFrameState
& aPullFrameState
,
709 const AutoRubyTextContainerArray
& textContainers
=
710 aPullFrameState
.mTextContainers
;
711 const uint32_t rtcCount
= textContainers
.Length();
713 nsIFrame
* nextBase
= GetNextInFlowChild(aPullFrameState
.mBase
);
714 MOZ_ASSERT(!nextBase
|| nextBase
->IsRubyBaseFrame());
715 aColumn
.mBaseFrame
= static_cast<nsRubyBaseFrame
*>(nextBase
);
716 bool foundFrame
= !!aColumn
.mBaseFrame
;
717 bool pullingIntraLevelWhitespace
=
718 aColumn
.mBaseFrame
&& aColumn
.mBaseFrame
->IsIntraLevelWhitespace();
720 aColumn
.mTextFrames
.ClearAndRetainStorage();
721 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
723 textContainers
[i
]->GetNextInFlowChild(aPullFrameState
.mTexts
[i
]);
724 MOZ_ASSERT(!nextText
|| nextText
->IsRubyTextFrame());
725 nsRubyTextFrame
* textFrame
= static_cast<nsRubyTextFrame
*>(nextText
);
726 aColumn
.mTextFrames
.AppendElement(textFrame
);
727 foundFrame
= foundFrame
|| nextText
;
728 if (nextText
&& !pullingIntraLevelWhitespace
) {
729 pullingIntraLevelWhitespace
= textFrame
->IsIntraLevelWhitespace();
732 // If there exists any frame in continations, we haven't
733 // completed the reflow process.
734 aIsComplete
= !foundFrame
;
739 aColumn
.mIsIntraLevelWhitespace
= pullingIntraLevelWhitespace
;
740 if (pullingIntraLevelWhitespace
) {
741 // We are pulling an intra-level whitespace. Drop all frames which
742 // are not part of this intra-level whitespace column. (Those frames
743 // are really part of the *next* column, after the pulled one.)
744 if (aColumn
.mBaseFrame
&& !aColumn
.mBaseFrame
->IsIntraLevelWhitespace()) {
745 aColumn
.mBaseFrame
= nullptr;
747 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
748 nsRubyTextFrame
*& textFrame
= aColumn
.mTextFrames
[i
];
749 if (textFrame
&& !textFrame
->IsIntraLevelWhitespace()) {
754 // We are not pulling an intra-level whitespace, which means all
755 // elements we are going to pull can have non-whitespace content,
756 // which may contain float which we need to reparent.
757 MOZ_ASSERT(aColumn
.begin() != aColumn
.end(),
758 "Ruby column shouldn't be empty");
759 nsBlockFrame
* oldFloatCB
=
760 nsLayoutUtils::GetFloatContainingBlock(*aColumn
.begin());
762 MOZ_ASSERT(oldFloatCB
, "Must have found a float containing block");
763 for (nsIFrame
* frame
: aColumn
) {
764 MOZ_ASSERT(nsLayoutUtils::GetFloatContainingBlock(frame
) == oldFloatCB
,
765 "All frames in the same ruby column should share "
766 "the same old float containing block");
769 nsBlockFrame
* newFloatCB
= do_QueryFrame(aLineLayout
->LineContainerFrame());
770 MOZ_ASSERT(newFloatCB
, "Must have a float containing block");
771 if (oldFloatCB
!= newFloatCB
) {
772 for (nsIFrame
* frame
: aColumn
) {
773 newFloatCB
->ReparentFloats(frame
, oldFloatCB
, false);
778 // Pull the frames of this column.
779 if (aColumn
.mBaseFrame
) {
780 DebugOnly
<nsIFrame
*> pulled
= PullNextInFlowChild(aPullFrameState
.mBase
);
781 MOZ_ASSERT(pulled
== aColumn
.mBaseFrame
, "pulled a wrong frame?");
783 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
784 if (aColumn
.mTextFrames
[i
]) {
785 DebugOnly
<nsIFrame
*> pulled
=
786 textContainers
[i
]->PullNextInFlowChild(aPullFrameState
.mTexts
[i
]);
787 MOZ_ASSERT(pulled
== aColumn
.mTextFrames
[i
], "pulled a wrong frame?");
792 // We pulled frames from the next line, hence mark it dirty.
793 aLineLayout
->SetDirtyNextLine();
797 nscoord
nsRubyBaseContainerFrame::ReflowSpans(
798 const RubyReflowInput
& aReflowInput
) {
799 nscoord spanISize
= 0;
800 for (uint32_t i
= 0, iend
= aReflowInput
.mTextContainers
.Length(); i
< iend
;
802 nsRubyTextContainerFrame
* container
= aReflowInput
.mTextContainers
[i
];
803 if (!container
->IsSpanContainer()) {
807 nsIFrame
* rtFrame
= container
->PrincipalChildList().FirstChild();
808 nsReflowStatus reflowStatus
;
810 nsLineLayout
* lineLayout
= aReflowInput
.mTextReflowInputs
[i
]->mLineLayout
;
811 MOZ_ASSERT(lineLayout
->GetCurrentICoord() == 0,
812 "border/padding of rtc should have been suppressed");
813 lineLayout
->ReflowFrame(rtFrame
, reflowStatus
, nullptr, pushedFrame
);
814 MOZ_ASSERT(!reflowStatus
.IsInlineBreak() && !pushedFrame
,
815 "Any line break inside ruby box should has been suppressed");
816 spanISize
= std::max(spanISize
, lineLayout
->GetCurrentICoord());