1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code is subject to the terms of the Mozilla Public License
4 * version 2.0 (the "License"). You can obtain a copy of the License at
5 * http://mozilla.org/MPL/2.0/. */
7 /* rendering object for CSS "display: ruby-base-container" */
9 #include "nsRubyBaseContainerFrame.h"
10 #include "nsContentUtils.h"
11 #include "nsLineLayout.h"
12 #include "nsPresContext.h"
13 #include "nsStyleContext.h"
14 #include "nsStyleStructInlines.h"
15 #include "WritingModes.h"
16 #include "RubyUtils.h"
17 #include "mozilla/Maybe.h"
18 #include "mozilla/DebugOnly.h"
20 using namespace mozilla
;
22 //----------------------------------------------------------------------
24 // Frame class boilerplate
25 // =======================
27 NS_QUERYFRAME_HEAD(nsRubyBaseContainerFrame
)
28 NS_QUERYFRAME_ENTRY(nsRubyBaseContainerFrame
)
29 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
31 NS_IMPL_FRAMEARENA_HELPERS(nsRubyBaseContainerFrame
)
34 NS_NewRubyBaseContainerFrame(nsIPresShell
* aPresShell
,
35 nsStyleContext
* aContext
)
37 return new (aPresShell
) nsRubyBaseContainerFrame(aContext
);
41 //----------------------------------------------------------------------
43 // nsRubyBaseContainerFrame Method Implementations
44 // ===============================================
47 nsRubyBaseContainerFrame::GetType() const
49 return nsGkAtoms::rubyBaseContainerFrame
;
52 #ifdef DEBUG_FRAME_DUMP
54 nsRubyBaseContainerFrame::GetFrameName(nsAString
& aResult
) const
56 return MakeFrameName(NS_LITERAL_STRING("RubyBaseContainer"), aResult
);
61 * Ruby column is a unit consists of one ruby base and all ruby
62 * annotations paired with it.
63 * See http://dev.w3.org/csswg/css-ruby/#ruby-pairing
65 struct MOZ_STACK_CLASS
mozilla::RubyColumn
67 nsRubyBaseFrame
* mBaseFrame
;
68 nsAutoTArray
<nsRubyTextFrame
*, RTC_ARRAY_SIZE
> mTextFrames
;
69 bool mIsIntraLevelWhitespace
;
70 RubyColumn() : mBaseFrame(nullptr), mIsIntraLevelWhitespace(false) { }
73 class MOZ_STACK_CLASS RubyColumnEnumerator
76 RubyColumnEnumerator(nsRubyBaseContainerFrame
* aRBCFrame
,
77 const nsTArray
<nsRubyTextContainerFrame
*>& aRTCFrames
);
82 uint32_t GetLevelCount() const { return mFrames
.Length(); }
83 nsRubyContentFrame
* GetFrameAtLevel(uint32_t aIndex
) const;
84 void GetColumn(RubyColumn
& aColumn
) const;
87 // Frames in this array are NOT necessary part of the current column.
88 // When in doubt, use GetFrameAtLevel to access it.
89 // See GetFrameAtLevel() and Next() for more info.
90 nsAutoTArray
<nsRubyContentFrame
*, RTC_ARRAY_SIZE
+ 1> mFrames
;
91 // Whether we are on a column for intra-level whitespaces
92 bool mAtIntraLevelWhitespace
;
95 RubyColumnEnumerator::RubyColumnEnumerator(
96 nsRubyBaseContainerFrame
* aBaseContainer
,
97 const nsTArray
<nsRubyTextContainerFrame
*>& aTextContainers
)
98 : mAtIntraLevelWhitespace(false)
100 const uint32_t rtcCount
= aTextContainers
.Length();
101 mFrames
.SetCapacity(rtcCount
+ 1);
103 nsIFrame
* rbFrame
= aBaseContainer
->GetFirstPrincipalChild();
104 MOZ_ASSERT(!rbFrame
|| rbFrame
->GetType() == nsGkAtoms::rubyBaseFrame
);
105 mFrames
.AppendElement(static_cast<nsRubyContentFrame
*>(rbFrame
));
106 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
107 nsRubyTextContainerFrame
* container
= aTextContainers
[i
];
108 // If the container is for span, leave a nullptr here.
109 // Spans do not take part in pairing.
110 nsIFrame
* rtFrame
= !container
->IsSpanContainer() ?
111 container
->GetFirstPrincipalChild() : nullptr;
112 MOZ_ASSERT(!rtFrame
|| rtFrame
->GetType() == nsGkAtoms::rubyTextFrame
);
113 mFrames
.AppendElement(static_cast<nsRubyContentFrame
*>(rtFrame
));
116 // We have to init mAtIntraLevelWhitespace to be correct for the
117 // first column. There are two ways we could end up with intra-level
118 // whitespace in our first colum:
119 // 1. The current segment itself is an inter-segment whitespace;
120 // 2. If our ruby segment is split across multiple lines, and some
121 // intra-level whitespace happens to fall right after a line-break.
122 // Each line will get its own nsRubyBaseContainerFrame, and the
123 // container right after the line-break will end up with its first
124 // column containing that intra-level whitespace.
125 for (uint32_t i
= 0, iend
= mFrames
.Length(); i
< iend
; i
++) {
126 nsRubyContentFrame
* frame
= mFrames
[i
];
127 if (frame
&& frame
->IsIntraLevelWhitespace()) {
128 mAtIntraLevelWhitespace
= true;
135 RubyColumnEnumerator::Next()
137 bool advancingToIntraLevelWhitespace
= false;
138 for (uint32_t i
= 0, iend
= mFrames
.Length(); i
< iend
; i
++) {
139 nsRubyContentFrame
* frame
= mFrames
[i
];
140 // If we've got intra-level whitespace frames at some levels in the
141 // current ruby column, we "faked" an anonymous box for all other
142 // levels for this column. So when we advance off this column, we
143 // don't advance any of the frames in those levels, because we're
144 // just advancing across the "fake" frames.
145 if (frame
&& (!mAtIntraLevelWhitespace
||
146 frame
->IsIntraLevelWhitespace())) {
147 nsIFrame
* nextSibling
= frame
->GetNextSibling();
148 MOZ_ASSERT(!nextSibling
|| nextSibling
->GetType() == frame
->GetType(),
149 "Frame type should be identical among a level");
150 mFrames
[i
] = frame
= static_cast<nsRubyContentFrame
*>(nextSibling
);
151 if (!advancingToIntraLevelWhitespace
&&
152 frame
&& frame
->IsIntraLevelWhitespace()) {
153 advancingToIntraLevelWhitespace
= true;
157 MOZ_ASSERT(!advancingToIntraLevelWhitespace
|| !mAtIntraLevelWhitespace
,
158 "Should never have adjacent intra-level whitespace columns");
159 mAtIntraLevelWhitespace
= advancingToIntraLevelWhitespace
;
163 RubyColumnEnumerator::AtEnd() const
165 for (uint32_t i
= 0, iend
= mFrames
.Length(); i
< iend
; i
++) {
174 RubyColumnEnumerator::GetFrameAtLevel(uint32_t aIndex
) const
176 // If the current ruby column is for intra-level whitespaces, we
177 // return nullptr for any levels that do not have an actual intra-
178 // level whitespace frame in this column. This nullptr represents
179 // an anonymous empty intra-level whitespace box. (In this case,
180 // it's important that we NOT return mFrames[aIndex], because it's
181 // really part of the next column, not the current one.)
182 nsRubyContentFrame
* frame
= mFrames
[aIndex
];
183 return !mAtIntraLevelWhitespace
||
184 (frame
&& frame
->IsIntraLevelWhitespace()) ? frame
: nullptr;
188 RubyColumnEnumerator::GetColumn(RubyColumn
& aColumn
) const
190 nsRubyContentFrame
* rbFrame
= GetFrameAtLevel(0);
191 MOZ_ASSERT(!rbFrame
|| rbFrame
->GetType() == nsGkAtoms::rubyBaseFrame
);
192 aColumn
.mBaseFrame
= static_cast<nsRubyBaseFrame
*>(rbFrame
);
193 aColumn
.mTextFrames
.ClearAndRetainStorage();
194 for (uint32_t i
= 1, iend
= mFrames
.Length(); i
< iend
; i
++) {
195 nsRubyContentFrame
* rtFrame
= GetFrameAtLevel(i
);
196 MOZ_ASSERT(!rtFrame
|| rtFrame
->GetType() == nsGkAtoms::rubyTextFrame
);
197 aColumn
.mTextFrames
.AppendElement(static_cast<nsRubyTextFrame
*>(rtFrame
));
199 aColumn
.mIsIntraLevelWhitespace
= mAtIntraLevelWhitespace
;
203 CalculateColumnPrefISize(nsRenderingContext
* aRenderingContext
,
204 const RubyColumnEnumerator
& aEnumerator
)
207 uint32_t levelCount
= aEnumerator
.GetLevelCount();
208 for (uint32_t i
= 0; i
< levelCount
; i
++) {
209 nsIFrame
* frame
= aEnumerator
.GetFrameAtLevel(i
);
211 max
= std::max(max
, frame
->GetPrefISize(aRenderingContext
));
218 nsRubyBaseContainerFrame::AddInlineMinISize(
219 nsRenderingContext
*aRenderingContext
, nsIFrame::InlineMinISizeData
*aData
)
221 AutoTextContainerArray textContainers
;
222 GetTextContainers(textContainers
);
224 for (uint32_t i
= 0, iend
= textContainers
.Length(); i
< iend
; i
++) {
225 if (textContainers
[i
]->IsSpanContainer()) {
226 // Since spans are not breakable internally, use our pref isize
227 // directly if there is any span.
228 aData
->currentLine
+= GetPrefISize(aRenderingContext
);
234 RubyColumnEnumerator
enumerator(this, textContainers
);
235 for (; !enumerator
.AtEnd(); enumerator
.Next()) {
236 // We use *pref* isize for computing the min isize of columns
237 // because ruby bases and texts are unbreakable internally.
238 max
= std::max(max
, CalculateColumnPrefISize(aRenderingContext
,
241 aData
->currentLine
+= max
;
245 nsRubyBaseContainerFrame::AddInlinePrefISize(
246 nsRenderingContext
*aRenderingContext
, nsIFrame::InlinePrefISizeData
*aData
)
248 AutoTextContainerArray textContainers
;
249 GetTextContainers(textContainers
);
252 RubyColumnEnumerator
enumerator(this, textContainers
);
253 for (; !enumerator
.AtEnd(); enumerator
.Next()) {
254 sum
+= CalculateColumnPrefISize(aRenderingContext
, enumerator
);
256 for (uint32_t i
= 0, iend
= textContainers
.Length(); i
< iend
; i
++) {
257 if (textContainers
[i
]->IsSpanContainer()) {
258 nsIFrame
* frame
= textContainers
[i
]->GetFirstPrincipalChild();
259 sum
= std::max(sum
, frame
->GetPrefISize(aRenderingContext
));
262 aData
->currentLine
+= sum
;
266 nsRubyBaseContainerFrame::IsFrameOfType(uint32_t aFlags
) const
268 return nsContainerFrame::IsFrameOfType(aFlags
&
269 ~(nsIFrame::eLineParticipant
));
273 nsRubyBaseContainerFrame::GetTextContainers(TextContainerArray
& aTextContainers
)
275 MOZ_ASSERT(aTextContainers
.IsEmpty());
276 for (RubyTextContainerIterator
iter(this); !iter
.AtEnd(); iter
.Next()) {
277 aTextContainers
.AppendElement(iter
.GetTextContainer());
282 nsRubyBaseContainerFrame::CanContinueTextRun() const
287 /* virtual */ LogicalSize
288 nsRubyBaseContainerFrame::ComputeSize(nsRenderingContext
*aRenderingContext
,
290 const LogicalSize
& aCBSize
,
291 nscoord aAvailableISize
,
292 const LogicalSize
& aMargin
,
293 const LogicalSize
& aBorder
,
294 const LogicalSize
& aPadding
,
295 ComputeSizeFlags aFlags
)
297 // Ruby base container frame is inline,
298 // hence don't compute size before reflow.
299 return LogicalSize(aWM
, NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
);
302 /* virtual */ nscoord
303 nsRubyBaseContainerFrame::GetLogicalBaseline(WritingMode aWritingMode
) const
308 struct nsRubyBaseContainerFrame::ReflowState
310 bool mAllowLineBreak
;
311 const TextContainerArray
& mTextContainers
;
312 const nsHTMLReflowState
& mBaseReflowState
;
313 const nsTArray
<UniquePtr
<nsHTMLReflowState
>>& mTextReflowStates
;
316 // Check whether the given extra isize can fit in the line in base level.
318 ShouldBreakBefore(const nsHTMLReflowState
& aReflowState
, nscoord aExtraISize
)
320 nsLineLayout
* lineLayout
= aReflowState
.mLineLayout
;
322 gfxBreakPriority priority
;
323 nscoord icoord
= lineLayout
->GetCurrentICoord();
324 return icoord
+ aExtraISize
> aReflowState
.AvailableISize() &&
325 lineLayout
->GetLastOptionalBreakPosition(&offset
, &priority
);
329 nsRubyBaseContainerFrame::Reflow(nsPresContext
* aPresContext
,
330 nsHTMLReflowMetrics
& aDesiredSize
,
331 const nsHTMLReflowState
& aReflowState
,
332 nsReflowStatus
& aStatus
)
334 DO_GLOBAL_REFLOW_COUNT("nsRubyBaseContainerFrame");
335 DISPLAY_REFLOW(aPresContext
, this, aReflowState
, aDesiredSize
, aStatus
);
336 aStatus
= NS_FRAME_COMPLETE
;
338 if (!aReflowState
.mLineLayout
) {
340 aReflowState
.mLineLayout
,
341 "No line layout provided to RubyBaseContainerFrame reflow method.");
344 MOZ_ASSERT(aReflowState
.mRubyReflowState
, "No ruby reflow state provided");
346 AutoTextContainerArray textContainers
;
347 GetTextContainers(textContainers
);
349 MoveOverflowToChildList();
350 // Ask text containers to drain overflows
351 const uint32_t rtcCount
= textContainers
.Length();
352 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
353 textContainers
[i
]->MoveOverflowToChildList();
356 WritingMode lineWM
= aReflowState
.mLineLayout
->GetWritingMode();
357 LogicalSize
availSize(lineWM
, aReflowState
.AvailableWidth(),
358 aReflowState
.AvailableHeight());
360 // We have a reflow state and a line layout for each RTC.
361 // They are conceptually the state of the RTCs, but we don't actually
362 // reflow those RTCs in this code. These two arrays are holders of
363 // the reflow states and line layouts.
364 // Since there are pointers refer to reflow states and line layouts,
365 // it is necessary to guarantee that they won't be moved. For this
366 // reason, they are wrapped in UniquePtr here.
367 nsAutoTArray
<UniquePtr
<nsHTMLReflowState
>, RTC_ARRAY_SIZE
> reflowStates
;
368 nsAutoTArray
<UniquePtr
<nsLineLayout
>, RTC_ARRAY_SIZE
> lineLayouts
;
369 reflowStates
.SetCapacity(rtcCount
);
370 lineLayouts
.SetCapacity(rtcCount
);
372 // Begin the line layout for each ruby text container in advance.
373 bool hasSpan
= false;
374 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
375 nsRubyTextContainerFrame
* textContainer
= textContainers
[i
];
376 if (textContainer
->IsSpanContainer()) {
380 nsHTMLReflowState
* reflowState
= new nsHTMLReflowState(
381 aPresContext
, *aReflowState
.parentReflowState
, textContainer
, availSize
);
382 reflowStates
.AppendElement(reflowState
);
383 nsLineLayout
* lineLayout
= new nsLineLayout(aPresContext
,
384 reflowState
->mFloatManager
,
385 reflowState
, nullptr,
386 aReflowState
.mLineLayout
);
387 lineLayouts
.AppendElement(lineLayout
);
389 // Line number is useless for ruby text
390 // XXX nullptr here may cause problem, see comments for
391 // nsLineLayout::mBlockRS and nsLineLayout::AddFloat
392 lineLayout
->Init(nullptr, reflowState
->CalcLineHeight(), -1);
393 reflowState
->mLineLayout
= lineLayout
;
395 LogicalMargin borderPadding
= reflowState
->ComputedLogicalBorderPadding();
396 nscoord containerWidth
=
397 reflowState
->ComputedWidth() + borderPadding
.LeftRight(lineWM
);
399 lineLayout
->BeginLineReflow(borderPadding
.IStart(lineWM
),
400 borderPadding
.BStart(lineWM
),
401 reflowState
->ComputedISize(),
402 NS_UNCONSTRAINEDSIZE
,
403 false, false, lineWM
, containerWidth
);
404 lineLayout
->AttachRootFrameToBaseLineLayout();
407 WritingMode frameWM
= aReflowState
.GetWritingMode();
408 LogicalMargin borderPadding
= aReflowState
.ComputedLogicalBorderPadding();
409 nscoord startEdge
= borderPadding
.IStart(frameWM
);
410 nscoord endEdge
= aReflowState
.AvailableISize() - borderPadding
.IEnd(frameWM
);
411 aReflowState
.mLineLayout
->BeginSpan(this, &aReflowState
,
412 startEdge
, endEdge
, &mBaseline
);
414 nsIFrame
* parent
= GetParent();
415 bool inNestedRuby
= parent
->StyleContext()->IsInlineDescendantOfRuby();
416 // Allow line break between ruby bases when white-space allows,
417 // we are not inside a nested ruby, and there is no span.
418 bool allowLineBreak
= !inNestedRuby
&& StyleText()->WhiteSpaceCanWrap(this);
419 bool allowInitialLineBreak
= allowLineBreak
;
420 if (!GetPrevInFlow()) {
421 allowInitialLineBreak
= !inNestedRuby
&&
422 parent
->StyleText()->WhiteSpaceCanWrap(parent
);
424 if (allowInitialLineBreak
&& aReflowState
.mLineLayout
->LineIsBreakable() &&
425 aReflowState
.mLineLayout
->NotifyOptionalBreakPosition(
426 this, 0, startEdge
<= aReflowState
.AvailableISize(),
427 gfxBreakPriority::eNormalBreak
)) {
428 aStatus
= NS_INLINE_LINE_BREAK_BEFORE();
432 if (aStatus
== NS_FRAME_COMPLETE
) {
433 // Reflow columns excluding any span
434 ReflowState reflowState
= {
435 allowLineBreak
&& !hasSpan
, textContainers
, aReflowState
, reflowStates
437 isize
= ReflowColumns(reflowState
, aStatus
);
439 DebugOnly
<nscoord
> lineSpanSize
= aReflowState
.mLineLayout
->EndSpan(this);
440 aDesiredSize
.ISize(lineWM
) = isize
;
441 // When there are no frames inside the ruby base container, EndSpan
442 // will return 0. However, in this case, the actual width of the
443 // container could be non-zero because of non-empty ruby annotations.
444 MOZ_ASSERT(NS_INLINE_IS_BREAK_BEFORE(aStatus
) ||
445 isize
== lineSpanSize
|| mFrames
.IsEmpty());
447 // If there exists any span, the columns must either be completely
448 // reflowed, or be not reflowed at all.
449 MOZ_ASSERT(NS_INLINE_IS_BREAK_BEFORE(aStatus
) ||
450 NS_FRAME_IS_COMPLETE(aStatus
) || !hasSpan
);
451 if (!NS_INLINE_IS_BREAK_BEFORE(aStatus
) &&
452 NS_FRAME_IS_COMPLETE(aStatus
) && hasSpan
) {
454 ReflowState reflowState
= {
455 false, textContainers
, aReflowState
, reflowStates
457 nscoord spanISize
= ReflowSpans(reflowState
);
458 nscoord deltaISize
= spanISize
- isize
;
459 if (deltaISize
> 0) {
460 if (allowLineBreak
&& ShouldBreakBefore(aReflowState
, deltaISize
)) {
461 aStatus
= NS_INLINE_LINE_BREAK_BEFORE();
466 // When there are spans, ReflowColumns and ReflowOneColumn won't
467 // record any optional break position. We have to record one
468 // at the end of this segment.
469 if (!NS_INLINE_IS_BREAK(aStatus
) && allowLineBreak
&&
470 aReflowState
.mLineLayout
->NotifyOptionalBreakPosition(
471 this, INT32_MAX
, startEdge
+ isize
<= aReflowState
.AvailableISize(),
472 gfxBreakPriority::eNormalBreak
)) {
473 aStatus
= NS_INLINE_LINE_BREAK_AFTER(aStatus
);
477 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
478 // It happens before the ruby text container is reflowed, and that
479 // when it is reflowed, it will just use this size.
480 nsRubyTextContainerFrame
* textContainer
= textContainers
[i
];
481 nsLineLayout
* lineLayout
= lineLayouts
[i
].get();
483 RubyUtils::ClearReservedISize(textContainer
);
484 nscoord rtcISize
= lineLayout
->GetCurrentICoord();
485 // Only span containers and containers with collapsed annotations
486 // need reserving isize. For normal ruby text containers, their
487 // children will be expanded properly. We only need to expand their
489 if (!textContainer
->IsSpanContainer()) {
491 } else if (isize
> rtcISize
) {
492 RubyUtils::SetReservedISize(textContainer
, isize
- rtcISize
);
495 lineLayout
->VerticalAlignLine();
496 LogicalSize
lineSize(lineWM
, rtcISize
, lineLayout
->GetFinalLineBSize());
497 aReflowState
.mRubyReflowState
->SetTextContainerInfo(i
, textContainer
, lineSize
);
498 lineLayout
->EndLineReflow();
501 nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize
, aReflowState
,
502 borderPadding
, lineWM
, frameWM
);
506 * This struct stores the continuations after this frame and
507 * corresponding text containers. It is used to speed up looking
508 * ahead for nonempty continuations.
510 struct MOZ_STACK_CLASS
nsRubyBaseContainerFrame::PullFrameState
512 ContinuationTraversingState mBase
;
513 nsAutoTArray
<ContinuationTraversingState
, RTC_ARRAY_SIZE
> mTexts
;
514 const TextContainerArray
& mTextContainers
;
516 PullFrameState(nsRubyBaseContainerFrame
* aBaseContainer
,
517 const TextContainerArray
& aTextContainers
);
521 nsRubyBaseContainerFrame::ReflowColumns(const ReflowState
& aReflowState
,
522 nsReflowStatus
& aStatus
)
524 nsLineLayout
* lineLayout
= aReflowState
.mBaseReflowState
.mLineLayout
;
525 const uint32_t rtcCount
= aReflowState
.mTextContainers
.Length();
526 nscoord istart
= lineLayout
->GetCurrentICoord();
527 nscoord icoord
= istart
;
528 nsReflowStatus reflowStatus
= NS_FRAME_COMPLETE
;
529 aStatus
= NS_FRAME_COMPLETE
;
531 uint32_t columnIndex
= 0;
533 column
.mTextFrames
.SetCapacity(rtcCount
);
534 RubyColumnEnumerator
e(this, aReflowState
.mTextContainers
);
535 for (; !e
.AtEnd(); e
.Next()) {
537 icoord
+= ReflowOneColumn(aReflowState
, columnIndex
, column
, reflowStatus
);
538 if (!NS_INLINE_IS_BREAK_BEFORE(reflowStatus
)) {
541 if (NS_INLINE_IS_BREAK(reflowStatus
)) {
544 // We are not handling overflow here.
545 MOZ_ASSERT(reflowStatus
== NS_FRAME_COMPLETE
);
548 bool isComplete
= false;
549 PullFrameState
pullFrameState(this, aReflowState
.mTextContainers
);
550 while (!NS_INLINE_IS_BREAK(reflowStatus
)) {
551 // We are not handling overflow here.
552 MOZ_ASSERT(reflowStatus
== NS_FRAME_COMPLETE
);
554 // Try pull some frames from next continuations. This call replaces
555 // frames in |column| with the frame pulled in each level.
556 PullOneColumn(lineLayout
, pullFrameState
, column
, isComplete
);
558 // No more frames can be pulled.
561 icoord
+= ReflowOneColumn(aReflowState
, columnIndex
, column
, reflowStatus
);
562 if (!NS_INLINE_IS_BREAK_BEFORE(reflowStatus
)) {
567 if (!e
.AtEnd() && NS_INLINE_IS_BREAK_AFTER(reflowStatus
)) {
568 // The current column has been successfully placed.
569 // Skip to the next column and mark break before.
572 reflowStatus
= NS_INLINE_LINE_BREAK_BEFORE();
574 if (!e
.AtEnd() || (GetNextInFlow() && !isComplete
)) {
575 NS_FRAME_SET_INCOMPLETE(aStatus
);
578 if (NS_INLINE_IS_BREAK_BEFORE(reflowStatus
)) {
579 if (!columnIndex
|| !aReflowState
.mAllowLineBreak
) {
580 // If no column has been placed yet, or we have any span,
581 // the whole container should be in the next line.
582 aStatus
= NS_INLINE_LINE_BREAK_BEFORE();
585 aStatus
= NS_INLINE_LINE_BREAK_AFTER(aStatus
);
586 MOZ_ASSERT(NS_FRAME_IS_COMPLETE(aStatus
) || aReflowState
.mAllowLineBreak
);
588 // If we are on an intra-level whitespace column, null values in
589 // column.mBaseFrame and column.mTextFrames don't represent the
590 // end of the frame-sibling-chain at that level -- instead, they
591 // represent an anonymous empty intra-level whitespace box. It is
592 // likely that there are frames in the next column (which can't be
593 // intra-level whitespace). Those frames should be pushed as well.
594 Maybe
<RubyColumn
> nextColumn
;
595 if (column
.mIsIntraLevelWhitespace
&& !e
.AtEnd()) {
597 nextColumn
.emplace();
598 e
.GetColumn(nextColumn
.ref());
600 nsIFrame
* baseFrame
= column
.mBaseFrame
;
601 if (!baseFrame
& nextColumn
.isSome()) {
602 baseFrame
= nextColumn
->mBaseFrame
;
605 PushChildren(baseFrame
, baseFrame
->GetPrevSibling());
607 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
608 nsRubyTextFrame
* textFrame
= column
.mTextFrames
[i
];
609 if (!textFrame
&& nextColumn
.isSome()) {
610 textFrame
= nextColumn
->mTextFrames
[i
];
613 aReflowState
.mTextContainers
[i
]->PushChildren(
614 textFrame
, textFrame
->GetPrevSibling());
617 } else if (NS_INLINE_IS_BREAK_AFTER(reflowStatus
)) {
618 // |reflowStatus| being break after here may only happen when
619 // there is a break after the column just pulled, or the whole
620 // segment has been completely reflowed. In those cases, we do
621 // not need to push anything.
622 MOZ_ASSERT(e
.AtEnd());
623 aStatus
= NS_INLINE_LINE_BREAK_AFTER(aStatus
);
626 return icoord
- istart
;
630 nsRubyBaseContainerFrame::ReflowOneColumn(const ReflowState
& aReflowState
,
631 uint32_t aColumnIndex
,
632 const RubyColumn
& aColumn
,
633 nsReflowStatus
& aStatus
)
635 const nsHTMLReflowState
& baseReflowState
= aReflowState
.mBaseReflowState
;
636 const auto& textReflowStates
= aReflowState
.mTextReflowStates
;
638 WritingMode lineWM
= baseReflowState
.mLineLayout
->GetWritingMode();
639 const uint32_t rtcCount
= aReflowState
.mTextContainers
.Length();
640 MOZ_ASSERT(aColumn
.mTextFrames
.Length() == rtcCount
);
641 MOZ_ASSERT(textReflowStates
.Length() == rtcCount
);
642 nscoord istart
= baseReflowState
.mLineLayout
->GetCurrentICoord();
643 nscoord columnISize
= 0;
645 nsAutoString baseText
;
646 if (aColumn
.mBaseFrame
) {
647 if (!nsContentUtils::GetNodeTextContent(aColumn
.mBaseFrame
->GetContent(),
649 NS_RUNTIMEABORT("OOM");
653 // Reflow text frames
654 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
655 nsRubyTextFrame
* textFrame
= aColumn
.mTextFrames
[i
];
657 nsAutoString annotationText
;
658 if (!nsContentUtils::GetNodeTextContent(textFrame
->GetContent(),
659 true, annotationText
)) {
660 NS_RUNTIMEABORT("OOM");
662 // Per CSS Ruby spec, the content comparison for auto-hiding
663 // takes place prior to white spaces collapsing (white-space)
664 // and text transformation (text-transform), and ignores elements
665 // (considers only the textContent of the boxes). Which means
666 // using the content tree text comparison is correct.
667 if (annotationText
.Equals(baseText
)) {
668 textFrame
->AddStateBits(NS_RUBY_TEXT_FRAME_AUTOHIDE
);
670 textFrame
->RemoveStateBits(NS_RUBY_TEXT_FRAME_AUTOHIDE
);
673 nsReflowStatus reflowStatus
;
674 nsHTMLReflowMetrics
metrics(*textReflowStates
[i
]);
675 RubyUtils::ClearReservedISize(textFrame
);
678 textReflowStates
[i
]->mLineLayout
->ReflowFrame(textFrame
, reflowStatus
,
679 &metrics
, pushedFrame
);
680 MOZ_ASSERT(!NS_INLINE_IS_BREAK(reflowStatus
) && !pushedFrame
,
681 "Any line break inside ruby box should has been suppressed");
682 columnISize
= std::max(columnISize
, metrics
.ISize(lineWM
));
685 if (aReflowState
.mAllowLineBreak
&&
686 ShouldBreakBefore(baseReflowState
, columnISize
)) {
687 // Since ruby text container uses an independent line layout, it
688 // may successfully place a frame because the line is empty while
689 // the line of base container is not.
690 aStatus
= NS_INLINE_LINE_BREAK_BEFORE();
694 // Reflow the base frame
695 if (aColumn
.mBaseFrame
) {
696 nsReflowStatus reflowStatus
;
697 nsHTMLReflowMetrics
metrics(baseReflowState
);
698 RubyUtils::ClearReservedISize(aColumn
.mBaseFrame
);
701 baseReflowState
.mLineLayout
->ReflowFrame(aColumn
.mBaseFrame
, reflowStatus
,
702 &metrics
, pushedFrame
);
703 MOZ_ASSERT(!NS_INLINE_IS_BREAK(reflowStatus
) && !pushedFrame
,
704 "Any line break inside ruby box should has been suppressed");
705 columnISize
= std::max(columnISize
, metrics
.ISize(lineWM
));
708 // Align all the line layout to the new coordinate.
709 nscoord icoord
= istart
+ columnISize
;
710 nscoord deltaISize
= icoord
- baseReflowState
.mLineLayout
->GetCurrentICoord();
711 if (deltaISize
> 0) {
712 baseReflowState
.mLineLayout
->AdvanceICoord(deltaISize
);
713 if (aColumn
.mBaseFrame
) {
714 RubyUtils::SetReservedISize(aColumn
.mBaseFrame
, deltaISize
);
717 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
718 if (aReflowState
.mTextContainers
[i
]->IsSpanContainer()) {
721 nsLineLayout
* lineLayout
= textReflowStates
[i
]->mLineLayout
;
722 nsRubyTextFrame
* textFrame
= aColumn
.mTextFrames
[i
];
723 nscoord deltaISize
= icoord
- lineLayout
->GetCurrentICoord();
724 if (deltaISize
> 0) {
725 lineLayout
->AdvanceICoord(deltaISize
);
727 RubyUtils::SetReservedISize(textFrame
, deltaISize
);
730 if (aColumn
.mBaseFrame
&& textFrame
) {
731 lineLayout
->AttachLastFrameToBaseLineLayout();
735 if (aReflowState
.mAllowLineBreak
&&
736 baseReflowState
.mLineLayout
->NotifyOptionalBreakPosition(
737 this, aColumnIndex
+ 1, icoord
<= baseReflowState
.AvailableISize(),
738 gfxBreakPriority::eNormalBreak
)) {
739 aStatus
= NS_INLINE_LINE_BREAK_AFTER(aStatus
);
745 nsRubyBaseContainerFrame::PullFrameState::PullFrameState(
746 nsRubyBaseContainerFrame
* aBaseContainer
,
747 const TextContainerArray
& aTextContainers
)
748 : mBase(aBaseContainer
)
749 , mTextContainers(aTextContainers
)
751 const uint32_t rtcCount
= aTextContainers
.Length();
752 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
753 mTexts
.AppendElement(aTextContainers
[i
]);
758 nsRubyBaseContainerFrame::PullOneColumn(nsLineLayout
* aLineLayout
,
759 PullFrameState
& aPullFrameState
,
763 const TextContainerArray
& textContainers
= aPullFrameState
.mTextContainers
;
764 const uint32_t rtcCount
= textContainers
.Length();
766 nsIFrame
* nextBase
= GetNextInFlowChild(aPullFrameState
.mBase
);
767 MOZ_ASSERT(!nextBase
|| nextBase
->GetType() == nsGkAtoms::rubyBaseFrame
);
768 aColumn
.mBaseFrame
= static_cast<nsRubyBaseFrame
*>(nextBase
);
769 aIsComplete
= !aColumn
.mBaseFrame
;
770 bool pullingIntraLevelWhitespace
=
771 aColumn
.mBaseFrame
&& aColumn
.mBaseFrame
->IsIntraLevelWhitespace();
773 aColumn
.mTextFrames
.ClearAndRetainStorage();
774 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
776 textContainers
[i
]->GetNextInFlowChild(aPullFrameState
.mTexts
[i
]);
777 MOZ_ASSERT(!nextText
|| nextText
->GetType() == nsGkAtoms::rubyTextFrame
);
778 nsRubyTextFrame
* textFrame
= static_cast<nsRubyTextFrame
*>(nextText
);
779 aColumn
.mTextFrames
.AppendElement(textFrame
);
780 // If there exists any frame in continations, we haven't
781 // completed the reflow process.
782 aIsComplete
= aIsComplete
&& !nextText
;
783 if (nextText
&& !pullingIntraLevelWhitespace
) {
784 pullingIntraLevelWhitespace
= textFrame
->IsIntraLevelWhitespace();
788 aColumn
.mIsIntraLevelWhitespace
= pullingIntraLevelWhitespace
;
789 if (pullingIntraLevelWhitespace
) {
790 // We are pulling an intra-level whitespace. Drop all frames which
791 // are not part of this intra-level whitespace column. (Those frames
792 // are really part of the *next* column, after the pulled one.)
793 if (aColumn
.mBaseFrame
&& !aColumn
.mBaseFrame
->IsIntraLevelWhitespace()) {
794 aColumn
.mBaseFrame
= nullptr;
796 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
797 nsRubyTextFrame
*& textFrame
= aColumn
.mTextFrames
[i
];
798 if (textFrame
&& !textFrame
->IsIntraLevelWhitespace()) {
804 // Pull the frames of this column.
805 if (aColumn
.mBaseFrame
) {
806 DebugOnly
<nsIFrame
*> pulled
= PullNextInFlowChild(aPullFrameState
.mBase
);
807 MOZ_ASSERT(pulled
== aColumn
.mBaseFrame
, "pulled a wrong frame?");
809 for (uint32_t i
= 0; i
< rtcCount
; i
++) {
810 if (aColumn
.mTextFrames
[i
]) {
811 DebugOnly
<nsIFrame
*> pulled
=
812 textContainers
[i
]->PullNextInFlowChild(aPullFrameState
.mTexts
[i
]);
813 MOZ_ASSERT(pulled
== aColumn
.mTextFrames
[i
], "pulled a wrong frame?");
818 // We pulled frames from the next line, hence mark it dirty.
819 aLineLayout
->SetDirtyNextLine();
824 nsRubyBaseContainerFrame::ReflowSpans(const ReflowState
& aReflowState
)
827 aReflowState
.mBaseReflowState
.mLineLayout
->GetWritingMode();
828 nscoord spanISize
= 0;
830 for (uint32_t i
= 0, iend
= aReflowState
.mTextContainers
.Length();
832 nsRubyTextContainerFrame
* container
= aReflowState
.mTextContainers
[i
];
833 if (!container
->IsSpanContainer()) {
837 nsIFrame
* rtFrame
= container
->GetFirstPrincipalChild();
838 nsReflowStatus reflowStatus
;
839 nsHTMLReflowMetrics
metrics(*aReflowState
.mTextReflowStates
[i
]);
841 aReflowState
.mTextReflowStates
[i
]->mLineLayout
->
842 ReflowFrame(rtFrame
, reflowStatus
, &metrics
, pushedFrame
);
843 MOZ_ASSERT(!NS_INLINE_IS_BREAK(reflowStatus
) && !pushedFrame
,
844 "Any line break inside ruby box should has been suppressed");
845 spanISize
= std::max(spanISize
, metrics
.ISize(lineWM
));