Bumping manifests a=b2g-bump
[gecko.git] / layout / generic / nsRubyBaseContainerFrame.cpp
blob0a613310b85296b26634af3bc81601f9688fe89d
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)
33 nsContainerFrame*
34 NS_NewRubyBaseContainerFrame(nsIPresShell* aPresShell,
35 nsStyleContext* aContext)
37 return new (aPresShell) nsRubyBaseContainerFrame(aContext);
41 //----------------------------------------------------------------------
43 // nsRubyBaseContainerFrame Method Implementations
44 // ===============================================
46 nsIAtom*
47 nsRubyBaseContainerFrame::GetType() const
49 return nsGkAtoms::rubyBaseContainerFrame;
52 #ifdef DEBUG_FRAME_DUMP
53 nsresult
54 nsRubyBaseContainerFrame::GetFrameName(nsAString& aResult) const
56 return MakeFrameName(NS_LITERAL_STRING("RubyBaseContainer"), aResult);
58 #endif
60 /**
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
75 public:
76 RubyColumnEnumerator(nsRubyBaseContainerFrame* aRBCFrame,
77 const nsTArray<nsRubyTextContainerFrame*>& aRTCFrames);
79 void Next();
80 bool AtEnd() const;
82 uint32_t GetLevelCount() const { return mFrames.Length(); }
83 nsRubyContentFrame* GetFrameAtLevel(uint32_t aIndex) const;
84 void GetColumn(RubyColumn& aColumn) const;
86 private:
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;
129 break;
134 void
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;
162 bool
163 RubyColumnEnumerator::AtEnd() const
165 for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
166 if (mFrames[i]) {
167 return false;
170 return true;
173 nsRubyContentFrame*
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;
187 void
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;
202 static nscoord
203 CalculateColumnPrefISize(nsRenderingContext* aRenderingContext,
204 const RubyColumnEnumerator& aEnumerator)
206 nscoord max = 0;
207 uint32_t levelCount = aEnumerator.GetLevelCount();
208 for (uint32_t i = 0; i < levelCount; i++) {
209 nsIFrame* frame = aEnumerator.GetFrameAtLevel(i);
210 if (frame) {
211 max = std::max(max, frame->GetPrefISize(aRenderingContext));
214 return max;
217 /* virtual */ void
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);
229 return;
233 nscoord max = 0;
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,
239 enumerator));
241 aData->currentLine += max;
244 /* virtual */ void
245 nsRubyBaseContainerFrame::AddInlinePrefISize(
246 nsRenderingContext *aRenderingContext, nsIFrame::InlinePrefISizeData *aData)
248 AutoTextContainerArray textContainers;
249 GetTextContainers(textContainers);
251 nscoord sum = 0;
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;
265 /* virtual */ bool
266 nsRubyBaseContainerFrame::IsFrameOfType(uint32_t aFlags) const
268 return nsContainerFrame::IsFrameOfType(aFlags &
269 ~(nsIFrame::eLineParticipant));
272 void
273 nsRubyBaseContainerFrame::GetTextContainers(TextContainerArray& aTextContainers)
275 MOZ_ASSERT(aTextContainers.IsEmpty());
276 for (RubyTextContainerIterator iter(this); !iter.AtEnd(); iter.Next()) {
277 aTextContainers.AppendElement(iter.GetTextContainer());
281 /* virtual */ bool
282 nsRubyBaseContainerFrame::CanContinueTextRun() const
284 return true;
287 /* virtual */ LogicalSize
288 nsRubyBaseContainerFrame::ComputeSize(nsRenderingContext *aRenderingContext,
289 WritingMode aWM,
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
305 return mBaseline;
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.
317 static bool
318 ShouldBreakBefore(const nsHTMLReflowState& aReflowState, nscoord aExtraISize)
320 nsLineLayout* lineLayout = aReflowState.mLineLayout;
321 int32_t offset;
322 gfxBreakPriority priority;
323 nscoord icoord = lineLayout->GetCurrentICoord();
324 return icoord + aExtraISize > aReflowState.AvailableISize() &&
325 lineLayout->GetLastOptionalBreakPosition(&offset, &priority);
328 /* virtual */ void
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) {
339 NS_ASSERTION(
340 aReflowState.mLineLayout,
341 "No line layout provided to RubyBaseContainerFrame reflow method.");
342 return;
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()) {
377 hasSpan = true;
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();
431 nscoord isize = 0;
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) {
453 // Reflow spans
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();
462 } else {
463 isize = spanISize;
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
488 // own size.
489 if (!textContainer->IsSpanContainer()) {
490 rtcISize = isize;
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);
520 nscoord
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;
532 RubyColumn column;
533 column.mTextFrames.SetCapacity(rtcCount);
534 RubyColumnEnumerator e(this, aReflowState.mTextContainers);
535 for (; !e.AtEnd(); e.Next()) {
536 e.GetColumn(column);
537 icoord += ReflowOneColumn(aReflowState, columnIndex, column, reflowStatus);
538 if (!NS_INLINE_IS_BREAK_BEFORE(reflowStatus)) {
539 columnIndex++;
541 if (NS_INLINE_IS_BREAK(reflowStatus)) {
542 break;
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);
557 if (isComplete) {
558 // No more frames can be pulled.
559 break;
561 icoord += ReflowOneColumn(aReflowState, columnIndex, column, reflowStatus);
562 if (!NS_INLINE_IS_BREAK_BEFORE(reflowStatus)) {
563 columnIndex++;
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.
570 e.Next();
571 e.GetColumn(column);
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();
583 return 0;
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()) {
596 e.Next();
597 nextColumn.emplace();
598 e.GetColumn(nextColumn.ref());
600 nsIFrame* baseFrame = column.mBaseFrame;
601 if (!baseFrame & nextColumn.isSome()) {
602 baseFrame = nextColumn->mBaseFrame;
604 if (baseFrame) {
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];
612 if (textFrame) {
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;
629 nscoord
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(),
648 true, baseText)) {
649 NS_RUNTIMEABORT("OOM");
653 // Reflow text frames
654 for (uint32_t i = 0; i < rtcCount; i++) {
655 nsRubyTextFrame* textFrame = aColumn.mTextFrames[i];
656 if (textFrame) {
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);
669 } else {
670 textFrame->RemoveStateBits(NS_RUBY_TEXT_FRAME_AUTOHIDE);
673 nsReflowStatus reflowStatus;
674 nsHTMLReflowMetrics metrics(*textReflowStates[i]);
675 RubyUtils::ClearReservedISize(textFrame);
677 bool pushedFrame;
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();
691 return 0;
694 // Reflow the base frame
695 if (aColumn.mBaseFrame) {
696 nsReflowStatus reflowStatus;
697 nsHTMLReflowMetrics metrics(baseReflowState);
698 RubyUtils::ClearReservedISize(aColumn.mBaseFrame);
700 bool pushedFrame;
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()) {
719 continue;
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);
726 if (textFrame) {
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);
742 return columnISize;
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]);
757 void
758 nsRubyBaseContainerFrame::PullOneColumn(nsLineLayout* aLineLayout,
759 PullFrameState& aPullFrameState,
760 RubyColumn& aColumn,
761 bool& aIsComplete)
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++) {
775 nsIFrame* nextText =
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()) {
799 textFrame = nullptr;
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?");
817 if (!aIsComplete) {
818 // We pulled frames from the next line, hence mark it dirty.
819 aLineLayout->SetDirtyNextLine();
823 nscoord
824 nsRubyBaseContainerFrame::ReflowSpans(const ReflowState& aReflowState)
826 WritingMode lineWM =
827 aReflowState.mBaseReflowState.mLineLayout->GetWritingMode();
828 nscoord spanISize = 0;
830 for (uint32_t i = 0, iend = aReflowState.mTextContainers.Length();
831 i < iend; i++) {
832 nsRubyTextContainerFrame* container = aReflowState.mTextContainers[i];
833 if (!container->IsSpanContainer()) {
834 continue;
837 nsIFrame* rtFrame = container->GetFirstPrincipalChild();
838 nsReflowStatus reflowStatus;
839 nsHTMLReflowMetrics metrics(*aReflowState.mTextReflowStates[i]);
840 bool pushedFrame;
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));
848 return spanISize;