Bug 1052924 - Implement basic line breaking for ruby. r=dbaron

Known problem:
It would cause infinite loop if there is any line break happens inside
ruby base or annotation, or the width of container is not enough for
the widest pair/span. This might be fixed in bug 1098272.
This commit is contained in:
Xidorn Quan 2014-11-26 15:52:50 +11:00
parent 5f4cd8dd0b
commit e464df4013
10 changed files with 482 additions and 60 deletions

View File

@ -8716,9 +8716,12 @@ nsCSSFrameConstructor::CreateContinuingFrame(nsPresContext* aPresContext,
} else if (nsGkAtoms::flexContainerFrame == frameType) {
newFrame = NS_NewFlexContainerFrame(shell, styleContext);
newFrame->Init(content, aParentFrame, aFrame);
//TODO: Add conditionals for rubyFrame and rubyBaseContainerFrame
// once their reflow methods are advanced enough to return
// non-complete statuses
} else if (nsGkAtoms::rubyFrame == frameType) {
newFrame = NS_NewRubyFrame(shell, styleContext);
newFrame->Init(content, aParentFrame, aFrame);
} else if (nsGkAtoms::rubyBaseContainerFrame == frameType) {
newFrame = NS_NewRubyBaseContainerFrame(shell, styleContext);
newFrame->Init(content, aParentFrame, aFrame);
} else if (nsGkAtoms::rubyTextContainerFrame == frameType) {
newFrame = NS_NewRubyTextContainerFrame(shell, styleContext);
newFrame->Init(content, aParentFrame, aFrame);

View File

@ -1517,6 +1517,59 @@ nsContainerFrame::DrainSelfOverflowList()
return false;
}
nsIFrame*
nsContainerFrame::GetNextInFlowChild(ContinuationTraversingState& aState,
bool* aIsInOverflow)
{
nsContainerFrame*& nextInFlow = aState.mNextInFlow;
while (nextInFlow) {
// See if there is any frame in the container
nsIFrame* frame = nextInFlow->mFrames.FirstChild();
if (frame) {
if (aIsInOverflow) {
*aIsInOverflow = false;
}
return frame;
}
// No frames in the principal list, try its overflow list
nsFrameList* overflowFrames = nextInFlow->GetOverflowFrames();
if (overflowFrames) {
if (aIsInOverflow) {
*aIsInOverflow = true;
}
return overflowFrames->FirstChild();
}
nextInFlow = static_cast<nsContainerFrame*>(nextInFlow->GetNextInFlow());
}
return nullptr;
}
nsIFrame*
nsContainerFrame::PullNextInFlowChild(ContinuationTraversingState& aState)
{
bool isInOverflow;
nsIFrame* frame = GetNextInFlowChild(aState, &isInOverflow);
if (frame) {
nsContainerFrame* nextInFlow = aState.mNextInFlow;
if (isInOverflow) {
nsFrameList* overflowFrames = nextInFlow->GetOverflowFrames();
overflowFrames->RemoveFirstChild();
if (overflowFrames->IsEmpty()) {
nextInFlow->DestroyOverflowList();
}
} else {
nextInFlow->mFrames.RemoveFirstChild();
}
// Move the frame to the principal frame list of this container
mFrames.AppendFrame(this, frame);
// AppendFrame has reparented the frame, we need
// to reparent the frame view then.
nsContainerFrame::ReparentFrameView(frame, nextInFlow, this);
}
return frame;
}
nsOverflowContinuationTracker::nsOverflowContinuationTracker(nsContainerFrame* aFrame,
bool aWalkOOFFrames,
bool aSkipOverflowContainerChildren)

View File

@ -526,6 +526,32 @@ protected:
*/
void PushChildren(nsIFrame* aFromChild, nsIFrame* aPrevSibling);
// ==========================================================================
/*
* Convenience methods for traversing continuations
*/
struct ContinuationTraversingState
{
nsContainerFrame* mNextInFlow;
ContinuationTraversingState(nsContainerFrame* aFrame)
: mNextInFlow(static_cast<nsContainerFrame*>(aFrame->GetNextInFlow()))
{ }
};
/**
* Find the first frame that is a child of this frame's next-in-flows,
* considering both their principal child lists and overflow lists.
*/
nsIFrame* GetNextInFlowChild(ContinuationTraversingState& aState,
bool* aIsInOverflow = nullptr);
/**
* Remove the result of GetNextInFlowChild from its current parent and
* append it to this frame's principal child list.
*/
nsIFrame* PullNextInFlowChild(ContinuationTraversingState& aState);
// ==========================================================================
/*
* Convenience methods for nsFrameLists stored in the

View File

@ -14,8 +14,6 @@
using namespace mozilla;
#define RTC_ARRAY_SIZE 1
//----------------------------------------------------------------------
// Frame class boilerplate
@ -67,7 +65,7 @@ public:
nsIFrame* GetFrame(uint32_t aIndex) const { return mFrames[aIndex]; }
nsIFrame* GetBaseFrame() const { return GetFrame(0); }
nsIFrame* GetTextFrame(uint32_t aIndex) const { return GetFrame(aIndex + 1); }
void GetTextFrames(nsTArray<nsIFrame*>& aFrames) const;
void GetFrames(nsIFrame*& aBaseFrame, nsTArray<nsIFrame*>& aTextFrames) const;
private:
nsAutoTArray<nsIFrame*, RTC_ARRAY_SIZE + 1> mFrames;
@ -108,11 +106,13 @@ PairEnumerator::AtEnd() const
}
void
PairEnumerator::GetTextFrames(nsTArray<nsIFrame*>& aFrames) const
PairEnumerator::GetFrames(nsIFrame*& aBaseFrame,
nsTArray<nsIFrame*>& aTextFrames) const
{
aFrames.ClearAndRetainStorage();
aBaseFrame = mFrames[0];
aTextFrames.ClearAndRetainStorage();
for (uint32_t i = 1, iend = mFrames.Length(); i < iend; i++) {
aFrames.AppendElement(mFrames[i]);
aTextFrames.AppendElement(mFrames[i]);
}
}
@ -191,14 +191,16 @@ void nsRubyBaseContainerFrame::AppendTextContainer(nsIFrame* aFrame)
nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(aFrame);
MOZ_ASSERT(rtcFrame, "Must provide a ruby text container.");
nsIFrame* onlyChild = rtcFrame->PrincipalChildList().OnlyChild();
if (onlyChild && onlyChild->IsPseudoFrame(rtcFrame->GetContent())) {
// Per CSS Ruby spec, if the only child of an rtc frame is
// a pseudo rt frame, it spans all bases in the segment.
mSpanContainers.AppendElement(rtcFrame);
} else {
mTextContainers.AppendElement(rtcFrame);
nsTArray<nsRubyTextContainerFrame*>* containers = &mTextContainers;
if (!GetPrevContinuation() && !GetNextContinuation()) {
nsIFrame* onlyChild = rtcFrame->PrincipalChildList().OnlyChild();
if (onlyChild && onlyChild->IsPseudoFrame(rtcFrame->GetContent())) {
// Per CSS Ruby spec, if the only child of an rtc frame is
// a pseudo rt frame, it spans all bases in the segment.
containers = &mSpanContainers;
}
}
containers->AppendElement(rtcFrame);
}
void nsRubyBaseContainerFrame::ClearTextContainers() {
@ -233,6 +235,18 @@ nsRubyBaseContainerFrame::GetLogicalBaseline(WritingMode aWritingMode) const
return mBaseline;
}
// Check whether the given extra isize can fit in the line in base level.
static bool
ShouldBreakBefore(const nsHTMLReflowState& aReflowState, nscoord aExtraISize)
{
nsLineLayout* lineLayout = aReflowState.mLineLayout;
int32_t offset;
gfxBreakPriority priority;
nscoord icoord = lineLayout->GetCurrentICoord();
return icoord + aExtraISize > aReflowState.AvailableISize() &&
lineLayout->GetLastOptionalBreakPosition(&offset, &priority);
}
/* virtual */ void
nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
@ -250,6 +264,13 @@ nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext,
return;
}
MoveOverflowToChildList();
// Ask text containers to drain overflows
const uint32_t rtcCount = mTextContainers.Length();
for (uint32_t i = 0; i < rtcCount; i++) {
mTextContainers[i]->MoveOverflowToChildList();
}
aStatus = NS_FRAME_COMPLETE;
WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
WritingMode frameWM = aReflowState.GetWritingMode();
@ -263,7 +284,6 @@ nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext,
LogicalSize availSize(lineWM, aReflowState.AvailableWidth(),
aReflowState.AvailableHeight());
const uint32_t rtcCount = mTextContainers.Length();
const uint32_t spanCount = mSpanContainers.Length();
const uint32_t totalCount = rtcCount + spanCount;
// We have a reflow state and a line layout for each RTC.
@ -316,24 +336,40 @@ nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext,
false, false, lineWM, containerWidth);
}
// Reflow non-span annotations and bases
// Reflow pairs excluding any span
nscoord pairsISize = ReflowPairs(aPresContext, aReflowState,
rtcReflowStates, aStatus);
nscoord isize = pairsISize;
// Reflow spans
nscoord spanISize = ReflowSpans(aPresContext, aReflowState,
spanReflowStates, aStatus);
if (isize < spanISize) {
aReflowState.mLineLayout->AdvanceICoord(spanISize - isize);
isize = spanISize;
// If there exists any span, the pairs must either be completely
// reflowed, or be not reflowed at all.
MOZ_ASSERT(NS_INLINE_IS_BREAK_BEFORE(aStatus) ||
NS_FRAME_IS_COMPLETE(aStatus) || mSpanContainers.IsEmpty());
if (!NS_INLINE_IS_BREAK_BEFORE(aStatus) &&
NS_FRAME_IS_COMPLETE(aStatus) && !mSpanContainers.IsEmpty()) {
// Reflow spans
nscoord spanISize = ReflowSpans(aPresContext, aReflowState,
spanReflowStates, aStatus);
// ReflowSpans never reports break or incomplete, but we still need
// to check if it exceed the line.
MOZ_ASSERT(aStatus == NS_FRAME_COMPLETE);
if (isize < spanISize) {
nscoord delta = spanISize - isize;
if (ShouldBreakBefore(aReflowState, delta)) {
aStatus = NS_INLINE_LINE_BREAK_BEFORE();
} else {
aReflowState.mLineLayout->AdvanceICoord(delta);
isize = spanISize;
}
}
}
DebugOnly<nscoord> lineSpanSize = aReflowState.mLineLayout->EndSpan(this);
// When there are no frames inside the ruby base container, EndSpan
// will return 0. However, in this case, the actual width of the
// container could be non-zero because of non-empty ruby annotations.
MOZ_ASSERT(isize == lineSpanSize || mFrames.IsEmpty());
MOZ_ASSERT(NS_INLINE_IS_BREAK_BEFORE(aStatus) ||
isize == lineSpanSize || mFrames.IsEmpty());
for (uint32_t i = 0; i < totalCount; i++) {
// It happens before the ruby text container is reflowed, and that
// when it is reflowed, it will just use this size.
@ -348,27 +384,126 @@ nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext,
borderPadding, lineWM, frameWM);
}
/**
* This struct stores the continuations after this frame and
* corresponding text containers. It is used to speed up looking
* ahead for nonempty continuations.
*/
struct MOZ_STACK_CLASS nsRubyBaseContainerFrame::PullFrameState
{
ContinuationTraversingState mBase;
nsAutoTArray<ContinuationTraversingState, RTC_ARRAY_SIZE> mTexts;
PullFrameState(nsRubyBaseContainerFrame* aFrame);
};
nscoord
nsRubyBaseContainerFrame::ReflowPairs(nsPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTArray<nsHTMLReflowState*>& aReflowStates,
nsReflowStatus& aStatus)
{
nscoord istart = aReflowState.mLineLayout->GetCurrentICoord();
nscoord icoord = istart;
nsLineLayout* lineLayout = aReflowState.mLineLayout;
if (!lineLayout->LineIsEmpty()) {
// Record break position only if the line is not empty.
if (lineLayout->NotifyOptionalBreakPosition(
this, 0, true, gfxBreakPriority::eNormalBreak)) {
aStatus = NS_INLINE_LINE_BREAK_BEFORE();
return 0;
}
}
const uint32_t rtcCount = mTextContainers.Length();
nscoord istart = lineLayout->GetCurrentICoord();
nscoord icoord = istart;
nsReflowStatus reflowStatus = NS_FRAME_COMPLETE;
aStatus = NS_FRAME_COMPLETE;
mPairCount = 0;
nsIFrame* baseFrame = nullptr;
nsAutoTArray<nsIFrame*, RTC_ARRAY_SIZE> textFrames;
textFrames.SetCapacity(mTextContainers.Length());
for (PairEnumerator e(this, mTextContainers); !e.AtEnd(); e.Next()) {
e.GetTextFrames(textFrames);
textFrames.SetCapacity(rtcCount);
PairEnumerator e(this, mTextContainers);
for (; !e.AtEnd(); e.Next()) {
e.GetFrames(baseFrame, textFrames);
icoord += ReflowOnePair(aPresContext, aReflowState, aReflowStates,
e.GetBaseFrame(), textFrames, aStatus);
baseFrame, textFrames, reflowStatus);
if (NS_INLINE_IS_BREAK(reflowStatus)) {
break;
}
// We are not handling overflow here.
MOZ_ASSERT(reflowStatus == NS_FRAME_COMPLETE);
}
bool isComplete = false;
PullFrameState pullFrameState(this);
while (!NS_INLINE_IS_BREAK(reflowStatus)) {
// We are not handling overflow here.
MOZ_ASSERT(reflowStatus == NS_FRAME_COMPLETE);
// Try pull some frames from next continuations. This call replaces
// |baseFrame| and |textFrames| with the frame pulled in each level.
PullOnePair(lineLayout, pullFrameState, baseFrame, textFrames, isComplete);
if (isComplete) {
// No more frames can be pulled.
break;
}
icoord += ReflowOnePair(aPresContext, aReflowState, aReflowStates,
baseFrame, textFrames, reflowStatus);
}
if (!e.AtEnd() && NS_INLINE_IS_BREAK_AFTER(reflowStatus)) {
// The current pair has been successfully placed.
// Skip to the next pair and mark break before.
e.Next();
e.GetFrames(baseFrame, textFrames);
reflowStatus = NS_INLINE_LINE_BREAK_BEFORE();
}
if (!e.AtEnd() || (GetNextInFlow() && !isComplete)) {
NS_FRAME_SET_INCOMPLETE(aStatus);
} else {
// Completely reflow the whole segment, record a break position.
// We record an extra break position here because ReflowOnePair
// won't record any break position if there exist spans.
if (lineLayout->NotifyOptionalBreakPosition(
this, INT32_MAX, true, gfxBreakPriority::eNormalBreak)) {
reflowStatus = NS_INLINE_LINE_BREAK_AFTER(reflowStatus);
}
}
if (NS_INLINE_IS_BREAK_BEFORE(reflowStatus)) {
if (!mPairCount || !mSpanContainers.IsEmpty()) {
// If no pair has been placed yet, or we have any span,
// the whole container should be in the next line.
aStatus = NS_INLINE_LINE_BREAK_BEFORE();
return 0;
}
aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
MOZ_ASSERT(NS_FRAME_IS_COMPLETE(aStatus) || mSpanContainers.IsEmpty());
if (baseFrame) {
PushChildren(baseFrame, baseFrame->GetPrevSibling());
}
for (uint32_t i = 0; i < rtcCount; i++) {
nsIFrame* textFrame = textFrames[i];
if (textFrame) {
mTextContainers[i]->PushChildren(textFrame,
textFrame->GetPrevSibling());
}
}
} else if (NS_INLINE_IS_BREAK_AFTER(reflowStatus)) {
// |reflowStatus| being break after here may only happen when
// there is a break after the pair just pulled, or the whole
// segment has been completely reflowed. In those cases, we do
// not need to push anything.
MOZ_ASSERT(e.AtEnd());
aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
}
return icoord - istart;
}
/* static */ nscoord
nscoord
nsRubyBaseContainerFrame::ReflowOnePair(nsPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTArray<nsHTMLReflowState*>& aReflowStates,
@ -377,11 +512,13 @@ nsRubyBaseContainerFrame::ReflowOnePair(nsPresContext* aPresContext,
nsReflowStatus& aStatus)
{
WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
const uint32_t rtcCount = aTextFrames.Length();
const uint32_t rtcCount = mTextContainers.Length();
MOZ_ASSERT(aTextFrames.Length() == rtcCount);
MOZ_ASSERT(aReflowStates.Length() == rtcCount);
nscoord istart = aReflowState.mLineLayout->GetCurrentICoord();
nscoord pairISize = 0;
// Reflow text frames
for (uint32_t i = 0; i < rtcCount; i++) {
if (aTextFrames[i]) {
MOZ_ASSERT(aTextFrames[i]->GetType() == nsGkAtoms::rubyTextFrame);
@ -391,13 +528,25 @@ nsRubyBaseContainerFrame::ReflowOnePair(nsPresContext* aPresContext,
bool pushedFrame;
aReflowStates[i]->mLineLayout->ReflowFrame(aTextFrames[i], reflowStatus,
&metrics, pushedFrame);
NS_ASSERTION(!NS_INLINE_IS_BREAK(reflowStatus),
"Ruby line breaking is not yet implemented");
NS_ASSERTION(!pushedFrame, "Ruby line breaking is not yet implemented");
if (NS_INLINE_IS_BREAK(reflowStatus)) {
// If any breaking occurs when reflowing a ruby text frame,
// we should abandon reflowing this pair.
aStatus = NS_INLINE_LINE_BREAK_BEFORE();
return 0;
}
MOZ_ASSERT(!pushedFrame, "Shouldn't push frame if there is no break");
pairISize = std::max(pairISize, metrics.ISize(lineWM));
}
}
if (ShouldBreakBefore(aReflowState, pairISize)) {
// Since ruby text container uses an independent line layout, it
// may successfully place a frame because the line is empty while
// the line of base container is not.
aStatus = NS_INLINE_LINE_BREAK_BEFORE();
return 0;
}
// Reflow the base frame
if (aBaseFrame) {
MOZ_ASSERT(aBaseFrame->GetType() == nsGkAtoms::rubyBaseFrame);
nsReflowStatus reflowStatus;
@ -406,9 +555,12 @@ nsRubyBaseContainerFrame::ReflowOnePair(nsPresContext* aPresContext,
bool pushedFrame;
aReflowState.mLineLayout->ReflowFrame(aBaseFrame, reflowStatus,
&metrics, pushedFrame);
NS_ASSERTION(!NS_INLINE_IS_BREAK(reflowStatus),
"Ruby line breaking is not yet implemented");
NS_ASSERTION(!pushedFrame, "Ruby line breaking is not yet implemented");
if (NS_INLINE_IS_BREAK(reflowStatus)) {
// We cannot place the ruby base frame. Abandon this pair.
aStatus = NS_INLINE_LINE_BREAK_BEFORE();
return 0;
}
MOZ_ASSERT(!pushedFrame, "Shouldn't push frame if there is no break");
pairISize = std::max(pairISize, metrics.ISize(lineWM));
}
@ -421,9 +573,57 @@ nsRubyBaseContainerFrame::ReflowOnePair(nsPresContext* aPresContext,
lineLayout->AdvanceICoord(icoord - lineLayout->GetCurrentICoord());
}
mPairCount++;
// We only break between bases if there is no span.
if (mSpanContainers.IsEmpty()) {
if (aReflowState.mLineLayout->NotifyOptionalBreakPosition(
this, mPairCount, icoord <= aReflowState.AvailableISize(),
gfxBreakPriority::eNormalBreak)) {
aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
}
}
return pairISize;
}
nsRubyBaseContainerFrame::PullFrameState::PullFrameState(
nsRubyBaseContainerFrame* aFrame)
: mBase(aFrame)
{
const uint32_t rtcCount = aFrame->mTextContainers.Length();
for (uint32_t i = 0; i < rtcCount; i++) {
mTexts.AppendElement(aFrame->mTextContainers[i]);
}
}
void
nsRubyBaseContainerFrame::PullOnePair(nsLineLayout* aLineLayout,
PullFrameState& aPullFrameState,
nsIFrame*& aBaseFrame,
nsTArray<nsIFrame*>& aTextFrames,
bool& aIsComplete)
{
const uint32_t rtcCount = mTextContainers.Length();
aBaseFrame = PullNextInFlowChild(aPullFrameState.mBase);
aIsComplete = !aBaseFrame;
aTextFrames.ClearAndRetainStorage();
for (uint32_t i = 0; i < rtcCount; i++) {
nsIFrame* nextText =
mTextContainers[i]->PullNextInFlowChild(aPullFrameState.mTexts[i]);
aTextFrames.AppendElement(nextText);
// If there exists any frame in continations, we haven't
// completed the reflow process.
aIsComplete = aIsComplete && !nextText;
}
if (!aIsComplete) {
// We pulled frames from the next line, hence mark it dirty.
aLineLayout->SetDirtyNextLine();
}
}
nscoord
nsRubyBaseContainerFrame::ReflowSpans(nsPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
@ -442,9 +642,9 @@ nsRubyBaseContainerFrame::ReflowSpans(nsPresContext* aPresContext,
bool pushedFrame;
aReflowStates[i]->mLineLayout->ReflowFrame(rtFrame, reflowStatus,
&metrics, pushedFrame);
NS_ASSERTION(!NS_INLINE_IS_BREAK(reflowStatus),
"Ruby line breaking is not yet implemented");
NS_ASSERTION(!pushedFrame, "Ruby line breaking is not yet implemented");
// It should never cause break, since it is always
// at the beginning in its line layout.
MOZ_ASSERT(!NS_INLINE_IS_BREAK(reflowStatus) && !pushedFrame);
spanISize = std::max(spanISize, metrics.ISize(lineWM));
}

View File

@ -14,6 +14,8 @@
#include "nsRubyBaseFrame.h"
#include "nsRubyTextFrame.h"
#define RTC_ARRAY_SIZE 1
/**
* Factory function.
* @return a newly allocated nsRubyBaseContainerFrame (infallible)
@ -81,18 +83,28 @@ protected:
nsTArray<nsHTMLReflowState*>& aReflowStates,
nsReflowStatus& aStatus);
static nscoord ReflowOnePair(nsPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTArray<nsHTMLReflowState*>& aReflowStates,
nsIFrame* aBaseFrame,
const nsTArray<nsIFrame*>& aTextFrames,
nsReflowStatus& aStatus);
nscoord ReflowOnePair(nsPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTArray<nsHTMLReflowState*>& aReflowStates,
nsIFrame* aBaseFrame,
const nsTArray<nsIFrame*>& aTextFrames,
nsReflowStatus& aStatus);
nscoord ReflowSpans(nsPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTArray<nsHTMLReflowState*>& aReflowStates,
nsReflowStatus& aStatus);
struct PullFrameState;
// Pull ruby base and corresponding ruby text frames from
// continuations after them.
void PullOnePair(nsLineLayout* aLineLayout,
PullFrameState& aPullFrameState,
nsIFrame*& aBaseFrame,
nsTArray<nsIFrame*>& aTextFrames,
bool& aIsComplete);
/**
* The arrays of ruby text containers below are filled before the ruby
* frame (parent) starts reflowing this ruby segment, and cleared when
@ -106,6 +118,7 @@ protected:
nsTArray<nsRubyTextContainerFrame*> mTextContainers;
nscoord mBaseline;
uint32_t mPairCount;
};
#endif /* nsRubyBaseContainerFrame_h___ */

View File

@ -229,6 +229,9 @@ nsRubyFrame::Reflow(nsPresContext* aPresContext,
return;
}
// Grab overflow frames from prev-in-flow and its own.
MoveOverflowToChildList();
// Begin the span for the ruby frame
WritingMode frameWM = aReflowState.GetWritingMode();
WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
@ -240,12 +243,29 @@ nsRubyFrame::Reflow(nsPresContext* aPresContext,
aReflowState.mLineLayout->BeginSpan(this, &aReflowState,
startEdge, endEdge, &mBaseline);
// FIXME: line breaking / continuations not yet implemented
aStatus = NS_FRAME_COMPLETE;
for (SegmentEnumerator e(this); !e.AtEnd(); e.Next()) {
ReflowSegment(aPresContext, aReflowState, e.GetBaseContainer(), aStatus);
if (NS_INLINE_IS_BREAK(aStatus)) {
// A break occurs when reflowing the segment.
// Don't continue reflowing more segments.
break;
}
}
ContinuationTraversingState pullState(this);
while (aStatus == NS_FRAME_COMPLETE) {
nsRubyBaseContainerFrame* baseContainer = PullOneSegment(pullState);
if (!baseContainer) {
// No more continuations after, finish now.
break;
}
ReflowSegment(aPresContext, aReflowState, baseContainer, aStatus);
}
// We never handle overflow in ruby.
MOZ_ASSERT(!NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus));
aDesiredSize.ISize(lineWM) = aReflowState.mLineLayout->EndSpan(this);
nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, aReflowState,
borderPadding, lineWM, frameWM);
@ -262,24 +282,100 @@ nsRubyFrame::ReflowSegment(nsPresContext* aPresContext,
LogicalSize availSize(lineWM, aReflowState.AvailableISize(),
aReflowState.AvailableBSize());
nsReflowStatus baseReflowStatus;
nsAutoTArray<nsRubyTextContainerFrame*, RTC_ARRAY_SIZE> textContainers;
for (TextContainerIterator iter(aBaseContainer); !iter.AtEnd(); iter.Next()) {
textContainers.AppendElement(iter.GetTextContainer());
}
const uint32_t rtcCount = textContainers.Length();
nsHTMLReflowMetrics baseMetrics(aReflowState);
bool pushedFrame;
aReflowState.mLineLayout->ReflowFrame(aBaseContainer, baseReflowStatus,
aReflowState.mLineLayout->ReflowFrame(aBaseContainer, aStatus,
&baseMetrics, pushedFrame);
if (NS_INLINE_IS_BREAK_BEFORE(aStatus)) {
if (aBaseContainer != mFrames.FirstChild()) {
// Some segments may have been reflowed before, hence it is not
// a break-before for the ruby container.
aStatus = NS_INLINE_LINE_BREAK_AFTER(NS_FRAME_NOT_COMPLETE);
PushChildren(aBaseContainer, aBaseContainer->GetPrevSibling());
aReflowState.mLineLayout->SetDirtyNextLine();
}
// This base container is not placed at all, we can skip all
// text containers paired with it.
return;
}
if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) {
// It always promise that if the status is incomplete, there is a
// break occurs. Break before has been processed above. However,
// it is possible that break after happens with the frame reflow
// completed. It happens if there is a force break at the end.
MOZ_ASSERT(NS_INLINE_IS_BREAK_AFTER(aStatus));
// Find the previous sibling which we will
// insert new continuations after.
nsIFrame* lastChild;
if (rtcCount > 0) {
lastChild = textContainers.LastElement();
} else {
lastChild = aBaseContainer;
}
// Create continuations for the base container
nsIFrame* newBaseContainer;
CreateNextInFlow(aBaseContainer, newBaseContainer);
// newBaseContainer is null if there are existing next-in-flows.
// We only need to move and push if there were not.
if (newBaseContainer) {
// Move the new frame after all the text containers
mFrames.RemoveFrame(newBaseContainer);
mFrames.InsertFrame(nullptr, lastChild, newBaseContainer);
// Create continuations for text containers
nsIFrame* newLastChild = newBaseContainer;
for (uint32_t i = 0; i < rtcCount; i++) {
nsIFrame* newTextContainer;
CreateNextInFlow(textContainers[i], newTextContainer);
MOZ_ASSERT(newTextContainer, "Next-in-flow of rtc should not exist "
"if the corresponding rbc does not");
mFrames.RemoveFrame(newTextContainer);
mFrames.InsertFrame(nullptr, newLastChild, newTextContainer);
newLastChild = newTextContainer;
}
PushChildren(newBaseContainer, lastChild);
aReflowState.mLineLayout->SetDirtyNextLine();
}
} else {
// If the ruby base container is reflowed completely, the line
// layout will remove the next-in-flows of that frame. But the
// line layout is not aware of the ruby text containers, hence
// it is necessary to remove them here.
for (uint32_t i = 0; i < rtcCount; i++) {
nsIFrame* nextRTC = textContainers[i]->GetNextInFlow();
if (nextRTC) {
nextRTC->GetParent()->DeleteNextInFlowChild(nextRTC, true);
}
}
}
nsRect baseRect = aBaseContainer->GetRect();
for (TextContainerIterator iter(aBaseContainer); !iter.AtEnd(); iter.Next()) {
nsRubyTextContainerFrame* textContainer = iter.GetTextContainer();
for (uint32_t i = 0; i < rtcCount; i++) {
nsRubyTextContainerFrame* textContainer = textContainers[i];
nsReflowStatus textReflowStatus;
nsHTMLReflowMetrics textMetrics(aReflowState);
nsHTMLReflowState textReflowState(aPresContext, aReflowState,
textContainer, availSize);
// FIXME We probably shouldn't be using the same nsLineLayout for
// the text containers. But it should be fine now as we are
// not actually using this line layout to reflow something,
// but just read the writing mode from it.
textReflowState.mLineLayout = aReflowState.mLineLayout;
textContainer->Reflow(aPresContext, textMetrics,
textReflowState, textReflowStatus);
// Ruby text containers always return NS_FRAME_COMPLETE even when
// they have continuations, because the breaking has already been
// handled when reflowing the base containers.
NS_ASSERTION(textReflowStatus == NS_FRAME_COMPLETE,
"Ruby line breaking is not yet implemented");
"Ruby text container must not break itself inside");
textContainer->SetSize(LogicalSize(lineWM, textMetrics.ISize(lineWM),
textMetrics.BSize(lineWM)));
nscoord x, y;
@ -295,3 +391,23 @@ nsRubyFrame::ReflowSegment(nsPresContext* aPresContext,
&textReflowState, x, y, 0);
}
}
nsRubyBaseContainerFrame*
nsRubyFrame::PullOneSegment(ContinuationTraversingState& aState)
{
// Pull a ruby base container
nsIFrame* baseFrame = PullNextInFlowChild(aState);
if (!baseFrame) {
return nullptr;
}
MOZ_ASSERT(baseFrame->GetType() == nsGkAtoms::rubyBaseContainerFrame);
// Pull all ruby text containers following the base container
nsIFrame* nextFrame;
while ((nextFrame = GetNextInFlowChild(aState)) != nullptr &&
nextFrame->GetType() == nsGkAtoms::rubyTextContainerFrame) {
PullNextInFlowChild(aState);
}
return static_cast<nsRubyBaseContainerFrame*>(baseFrame);
}

View File

@ -12,6 +12,7 @@
#include "nsContainerFrame.h"
class nsRubyBaseContainerFrame;
class nsRubyTextContainerFrame;
/**
* Factory function.
@ -65,6 +66,8 @@ protected:
nsRubyBaseContainerFrame* aBaseContainer,
nsReflowStatus& aStatus);
nsRubyBaseContainerFrame* PullOneSegment(ContinuationTraversingState& aState);
nscoord mBaseline;
};

View File

@ -70,14 +70,20 @@ nsRubyTextContainerFrame::Reflow(nsPresContext* aPresContext,
DO_GLOBAL_REFLOW_COUNT("nsRubyTextContainerFrame");
DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
// All rt children have already been reflowed. All we need to do is clean up
// the line layout.
// All rt children have already been reflowed. All we need to do is
// to report complete and return the desired size.
// Although a ruby text container may have continuations, returning
// NS_FRAME_COMPLETE here is still safe, since its parent, ruby frame,
// ignores the status, and continuations of the ruby base container
// will take care of our continuations.
aStatus = NS_FRAME_COMPLETE;
WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
WritingMode frameWM = aReflowState.GetWritingMode();
LogicalMargin borderPadding = aReflowState.ComputedLogicalBorderPadding();
// ISize is provided by the ruby base container
// during reflow of that container.
aDesiredSize.ISize(lineWM) = mISize;
nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, aReflowState,
borderPadding, lineWM, frameWM);

View File

@ -41,7 +41,6 @@ public:
#ifdef DEBUG_FRAME_DUMP
virtual nsresult GetFrameName(nsAString& aResult) const MOZ_OVERRIDE;
#endif
void SetISize(nscoord aISize) { mISize = aISize; }
protected:
friend nsContainerFrame*
@ -50,6 +49,9 @@ protected:
explicit nsRubyTextContainerFrame(nsStyleContext* aContext)
: nsRubyTextContainerFrameSuper(aContext) {}
friend class nsRubyBaseContainerFrame;
void SetISize(nscoord aISize) { mISize = aISize; }
// The intended dimensions of the ruby text container. These are modified
// whenever a ruby text box is reflowed and used when the ruby text container
// is reflowed.

View File

@ -1,6 +1,6 @@
default-preferences pref(layout.css.ruby.enabled,true)
asserts(3-20) == ruby-whitespace-1.html ruby-whitespace-1-ref.html # bug 1052924
== ruby-whitespace-1.html ruby-whitespace-1-ref.html
== ruby-whitespace-2.html ruby-whitespace-2-ref.html
asserts(0-1) != ruby-reflow-1-opaqueruby.html ruby-reflow-1-noruby.html # bug 1052924
asserts(0-1) == ruby-reflow-1-transparentruby.html ruby-reflow-1-noruby.html # bug 1052924
!= ruby-reflow-1-opaqueruby.html ruby-reflow-1-noruby.html
== ruby-reflow-1-transparentruby.html ruby-reflow-1-noruby.html