Bug 1052924 - Rewrite reflow code. r=dbaron

Important changes:
  * Avoid using GetPrefISize on the ruby texts in
    nsRubyBaseContainerFrame::Reflow, since the size it produces might
    not match the size produced by Reflow.  The old code calls that on
    all the ruby texts to determine how big they are, then reflows all
    the ruby bases, and then reflows all the ruby texts.  The new code
    instead processes one pair at a time, and for each pair reflows the
    ruby texts and then the ruby base.
  * Change the base class of nsRubyTextContainerFrame from nsBlockFrame
    to nsContainerFrame, and stop constructing an nsBlockReflowState for
    its reflow.
  * Move the code for reflowing ruby texts from nsRubyTextContainerFrame
    and to nsRubyBaseContainerFrame.
  * Fix the regression that ruby text containers contain span are not
    reflowed properly. It is the regression introduced in patch 0.

Known regression:
  * This patch drops centering ruby base and annotation in pairs. This
    should be fixed in bug 1055676 (ruby-align).
This commit is contained in:
Xidorn Quan 2014-11-26 15:52:49 +11:00
parent e254b3d884
commit c25136319a
4 changed files with 115 additions and 188 deletions

View File

@ -220,96 +220,138 @@ nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext,
}
aStatus = NS_FRAME_COMPLETE;
nscoord isize = 0;
nscoord leftoverSpace = 0;
nscoord spaceApart = 0;
WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
WritingMode frameWM = aReflowState.GetWritingMode();
LogicalMargin borderPadding =
aReflowState.ComputedLogicalBorderPadding();
nscoord baseStart = 0;
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.
// They are conceptually the state of the RTCs, but we don't actually
// reflow those RTCs in this code. These two arrays are holders of
// the reflow states and line layouts.
nsAutoTArray<UniquePtr<nsHTMLReflowState>, RTC_ARRAY_SIZE> reflowStates;
nsAutoTArray<UniquePtr<nsLineLayout>, RTC_ARRAY_SIZE> lineLayouts;
reflowStates.SetCapacity(totalCount);
lineLayouts.SetCapacity(totalCount);
nsAutoTArray<nsHTMLReflowState*, RTC_ARRAY_SIZE> rtcReflowStates;
nsAutoTArray<nsHTMLReflowState*, RTC_ARRAY_SIZE> spanReflowStates;
rtcReflowStates.SetCapacity(rtcCount);
spanReflowStates.SetCapacity(spanCount);
// Begin the line layout for each ruby text container in advance.
for (uint32_t i = 0; i < rtcCount; i++) {
nsRubyTextContainerFrame* rtcFrame = mTextContainers.ElementAt(i);
nsHTMLReflowState rtcReflowState(aPresContext,
*aReflowState.parentReflowState,
rtcFrame, availSize);
rtcReflowState.mLineLayout = aReflowState.mLineLayout;
// FIXME: Avoid using/needing the rtcReflowState argument
rtcFrame->BeginRTCLineLayout(aPresContext, rtcReflowState);
for (uint32_t i = 0; i < totalCount; i++) {
nsIFrame* textContainer;
nsTArray<nsHTMLReflowState*>* reflowStateArray;
if (i < rtcCount) {
textContainer = mTextContainers[i];
reflowStateArray = &rtcReflowStates;
} else {
textContainer = mSpanContainers[i - rtcCount];
reflowStateArray = &spanReflowStates;
}
nsHTMLReflowState* reflowState = new nsHTMLReflowState(
aPresContext, *aReflowState.parentReflowState, textContainer, availSize);
reflowStates.AppendElement(reflowState);
reflowStateArray->AppendElement(reflowState);
nsLineLayout* lineLayout = new nsLineLayout(
aPresContext, reflowState->mFloatManager, reflowState, nullptr);
lineLayouts.AppendElement(lineLayout);
// Line number is useless for ruby text
// XXX nullptr here may cause problem, see comments for
// nsLineLayout::mBlockRS and nsLineLayout::AddFloat
lineLayout->Init(nullptr, reflowState->CalcLineHeight(), -1);
reflowState->mLineLayout = lineLayout;
LogicalMargin borderPadding = reflowState->ComputedLogicalBorderPadding();
nscoord containerWidth =
reflowState->ComputedWidth() + borderPadding.LeftRight(lineWM);
lineLayout->BeginLineReflow(borderPadding.IStart(lineWM),
borderPadding.BStart(lineWM),
reflowState->ComputedISize(),
NS_UNCONSTRAINEDSIZE,
false, false, lineWM, containerWidth);
}
nscoord istart = aReflowState.mLineLayout->GetCurrentICoord();
nscoord icoord = istart;
// Reflow non-span annotations and bases
for (PairEnumerator e(this, mTextContainers); !e.AtEnd(); e.Next()) {
// Determine if we need more spacing between bases in the inline direction
// depending on the inline size of the corresponding annotations
// FIXME: The use of GetPrefISize here and below is easier but not ideal. It
// would be better to use metrics from reflow.
nscoord textWidth = 0;
nscoord pairISize = 0;
for (uint32_t i = 0; i < rtcCount; i++) {
nsRubyTextFrame* rtFrame = do_QueryFrame(e.GetTextFrame(i));
if (rtFrame) {
int newWidth = rtFrame->GetPrefISize(aReflowState.rendContext);
if (newWidth > textWidth) {
textWidth = newWidth;
}
nsReflowStatus reflowStatus;
nsHTMLReflowMetrics metrics(*rtcReflowStates[i]);
bool pushedFrame;
rtcReflowStates[i]->mLineLayout->ReflowFrame(rtFrame, reflowStatus,
&metrics, pushedFrame);
NS_ASSERTION(!pushedFrame, "Ruby line breaking is not yet implemented");
pairISize = std::max(pairISize, metrics.ISize(lineWM));
}
}
nsIFrame* rbFrame = e.GetBaseFrame();
NS_ASSERTION(!rbFrame || rbFrame->GetType() == nsGkAtoms::rubyBaseFrame,
"Unrecognized child type for ruby base container");
if (rbFrame) {
nsReflowStatus frameReflowStatus;
nsHTMLReflowMetrics metrics(aReflowState, aDesiredSize.mFlags);
nscoord prefWidth = rbFrame->GetPrefISize(aReflowState.rendContext);
if (textWidth > prefWidth) {
spaceApart = std::max((textWidth - prefWidth) / 2, spaceApart);
leftoverSpace = spaceApart;
} else {
spaceApart = leftoverSpace;
leftoverSpace = 0;
}
if (spaceApart > 0) {
aReflowState.mLineLayout->AdvanceICoord(spaceApart);
}
baseStart = aReflowState.mLineLayout->GetCurrentICoord();
MOZ_ASSERT(rbFrame->GetType() == nsGkAtoms::rubyBaseFrame);
nsReflowStatus reflowStatus;
nsHTMLReflowMetrics metrics(aReflowState);
bool pushedFrame;
aReflowState.mLineLayout->ReflowFrame(rbFrame, frameReflowStatus,
aReflowState.mLineLayout->ReflowFrame(rbFrame, reflowStatus,
&metrics, pushedFrame);
NS_ASSERTION(!pushedFrame, "Ruby line breaking is not yet implemented");
isize += metrics.ISize(lineWM);
rbFrame->SetSize(LogicalSize(lineWM, metrics.ISize(lineWM),
metrics.BSize(lineWM)));
FinishReflowChild(rbFrame, aPresContext, metrics, &aReflowState, 0, 0,
NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_MOVE_VIEW);
pairISize = std::max(pairISize, metrics.ISize(lineWM));
}
// Now reflow the ruby text boxes that correspond to this ruby base box.
// Align all the line layout to the new coordinate.
icoord += pairISize;
aReflowState.mLineLayout->AdvanceICoord(
icoord - aReflowState.mLineLayout->GetCurrentICoord());
for (uint32_t i = 0; i < rtcCount; i++) {
nsRubyTextFrame* rtFrame = do_QueryFrame(e.GetTextFrame(i));
nsRubyTextContainerFrame* rtcFrame = mTextContainers[i];
if (rtFrame) {
nsHTMLReflowMetrics rtcMetrics(*aReflowState.parentReflowState,
aDesiredSize.mFlags);
nsHTMLReflowState rtcReflowState(aPresContext,
*aReflowState.parentReflowState,
rtcFrame, availSize);
rtcReflowState.mLineLayout = rtcFrame->GetLineLayout();
rtcFrame->ReflowRubyTextFrame(rtFrame, rbFrame, baseStart,
aPresContext, rtcMetrics,
rtcReflowState);
}
nsLineLayout* lineLayout = rtcReflowStates[i]->mLineLayout;
lineLayout->AdvanceICoord(icoord - lineLayout->GetCurrentICoord());
}
}
// Reflow spans
nscoord spanISize = 0;
for (uint32_t i = 0; i < spanCount; i++) {
nsRubyTextContainerFrame* container = mSpanContainers[i];
nsIFrame* rtFrame = container->GetFirstPrincipalChild();
nsReflowStatus reflowStatus;
nsHTMLReflowMetrics metrics(*spanReflowStates[i]);
bool pushedFrame;
spanReflowStates[i]->mLineLayout->ReflowFrame(rtFrame, reflowStatus,
&metrics, pushedFrame);
NS_ASSERTION(!pushedFrame, "Ruby line breaking is not yet implemented");
spanISize = std::max(spanISize, metrics.ISize(lineWM));
}
nscoord isize = icoord - istart;
if (isize < spanISize) {
aReflowState.mLineLayout->AdvanceICoord(spanISize - isize);
isize = spanISize;
}
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.
nsRubyTextContainerFrame* textContainer = i < rtcCount ?
mTextContainers[i] : mSpanContainers[i - rtcCount];
textContainer->SetISize(isize);
lineLayouts[i]->EndLineReflow();
}
LogicalMargin borderPadding = aReflowState.ComputedLogicalBorderPadding();
aDesiredSize.ISize(lineWM) = isize;
nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, aReflowState,
borderPadding, lineWM, frameWM);

View File

@ -9,6 +9,7 @@
#ifndef nsRubyBaseContainerFrame_h___
#define nsRubyBaseContainerFrame_h___
#include "mozilla/UniquePtr.h"
#include "nsContainerFrame.h"
#include "nsRubyTextContainerFrame.h"
#include "nsRubyBaseFrame.h"

View File

@ -12,6 +12,8 @@
#include "WritingModes.h"
#include "mozilla/UniquePtr.h"
using namespace mozilla;
//----------------------------------------------------------------------
// Frame class boilerplate
@ -19,7 +21,7 @@
NS_QUERYFRAME_HEAD(nsRubyTextContainerFrame)
NS_QUERYFRAME_ENTRY(nsRubyTextContainerFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
NS_IMPL_FRAMEARENA_HELPERS(nsRubyTextContainerFrame)
@ -50,97 +52,6 @@ nsRubyTextContainerFrame::GetFrameName(nsAString& aResult) const
}
#endif
void
nsRubyTextContainerFrame::BeginRTCLineLayout(nsPresContext* aPresContext,
const nsHTMLReflowState& aReflowState)
{
// Construct block reflow state and line layout
nscoord consumedBSize = GetConsumedBSize();
ClearLineCursor();
mISize = 0;
nsBlockReflowState state(aReflowState, aPresContext, this, true, true,
false, consumedBSize);
NS_ASSERTION(!mLines.empty(),
"There should be at least one line in the ruby text container");
line_iterator firstLine = begin_lines();
mLineLayout = mozilla::MakeUnique<nsLineLayout>(
state.mPresContext,
state.mReflowState.mFloatManager,
&state.mReflowState, &firstLine);
mLineLayout->Init(&state, state.mMinLineHeight, state.mLineNumber);
mozilla::WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
mozilla::LogicalRect lineRect(state.mContentArea);
nscoord iStart = lineRect.IStart(lineWM);
nscoord availISize = lineRect.ISize(lineWM);
nscoord availBSize = NS_UNCONSTRAINEDSIZE;
mLineLayout->BeginLineReflow(iStart, state.mBCoord,
availISize, availBSize,
false,
false,
lineWM, state.mContainerWidth);
}
void
nsRubyTextContainerFrame::ReflowRubyTextFrame(
nsRubyTextFrame* rtFrame,
nsIFrame* rbFrame,
nscoord baseStart,
nsPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState)
{
nsReflowStatus frameReflowStatus;
nsHTMLReflowMetrics metrics(aReflowState, aDesiredSize.mFlags);
mozilla::WritingMode lineWM = mLineLayout->GetWritingMode();
mozilla::LogicalSize availSize(lineWM, aReflowState.AvailableWidth(),
aReflowState.AvailableHeight());
nsHTMLReflowState childReflowState(aPresContext, aReflowState, rtFrame, availSize);
// Determine the inline coordinate for the text frame by centering over
// the corresponding base frame
int baseWidth;
if (rbFrame) {
baseWidth = rbFrame->ISize();
// If this is the last ruby annotation, it gets paired with ALL remaining
// ruby bases
if (!rtFrame->GetNextSibling()) {
rbFrame = rbFrame->GetNextSibling();
while (rbFrame) {
baseWidth += rbFrame->ISize();
rbFrame = rbFrame->GetNextSibling();
}
}
} else {
baseWidth = 0;
}
int baseCenter = baseStart + baseWidth / 2;
// FIXME: Find a way to avoid using GetPrefISize here, potentially by moving
// the frame after it has reflowed.
nscoord ICoord = baseCenter - rtFrame->GetPrefISize(aReflowState.rendContext) / 2;
if (ICoord > mLineLayout->GetCurrentICoord()) {
mLineLayout->AdvanceICoord(ICoord - mLineLayout->GetCurrentICoord());
}
bool pushedFrame;
mLineLayout->ReflowFrame(rtFrame, frameReflowStatus,
&metrics, pushedFrame);
NS_ASSERTION(!pushedFrame, "Ruby line breaking is not yet implemented");
mISize += metrics.ISize(lineWM);
rtFrame->SetSize(nsSize(metrics.ISize(lineWM), metrics.BSize(lineWM)));
FinishReflowChild(rtFrame, aPresContext, metrics, &childReflowState, 0, 0,
NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_MOVE_VIEW);
}
/* virtual */ void
nsRubyTextContainerFrame::Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
@ -154,26 +65,11 @@ nsRubyTextContainerFrame::Reflow(nsPresContext* aPresContext,
// the line layout.
aStatus = NS_FRAME_COMPLETE;
mozilla::WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
mozilla::WritingMode frameWM = aReflowState.GetWritingMode();
mozilla::LogicalMargin borderPadding =
aReflowState.ComputedLogicalBorderPadding();
WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
WritingMode frameWM = aReflowState.GetWritingMode();
LogicalMargin borderPadding = aReflowState.ComputedLogicalBorderPadding();
aDesiredSize.ISize(lineWM) = mISize;
nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, aReflowState,
borderPadding, lineWM, frameWM);
nscoord bsize = aDesiredSize.BSize(lineWM);
if (!mLines.empty()) {
// Okay to use BlockStartAscent because it has just been correctly set by
// nsLayoutUtils::SetBSizeFromFontMetrics.
mLines.begin()->SetLogicalAscent(aDesiredSize.BlockStartAscent());
mLines.begin()->SetBounds(aReflowState.GetWritingMode(), 0, 0, mISize,
bsize, mISize);
}
if (mLineLayout) {
mLineLayout->EndLineReflow();
mLineLayout = nullptr;
}
}

View File

@ -21,10 +21,7 @@
nsContainerFrame* NS_NewRubyTextContainerFrame(nsIPresShell* aPresShell,
nsStyleContext* aContext);
// If this is ever changed to be inline again, the code in
// nsFrame::IsFontSizeInflationContainer should be updated to stop excluding
// this from being considered inline.
class nsRubyTextContainerFrame MOZ_FINAL : public nsBlockFrame
class nsRubyTextContainerFrame MOZ_FINAL : public nsContainerFrame
{
public:
NS_DECL_FRAMEARENA_HELPERS
@ -41,24 +38,15 @@ public:
#ifdef DEBUG_FRAME_DUMP
virtual nsresult GetFrameName(nsAString& aResult) const MOZ_OVERRIDE;
#endif
void ReflowRubyTextFrame(nsRubyTextFrame* rtFrame, nsIFrame* rbFrame,
nscoord baseStart, nsPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState);
void BeginRTCLineLayout(nsPresContext* aPresContext,
const nsHTMLReflowState& aReflowState);
nsLineLayout* GetLineLayout() { return mLineLayout.get(); };
void SetISize(nscoord aISize) { mISize = aISize; }
protected:
friend nsContainerFrame*
NS_NewRubyTextContainerFrame(nsIPresShell* aPresShell,
nsStyleContext* aContext);
explicit nsRubyTextContainerFrame(nsStyleContext* aContext) : nsBlockFrame(aContext) {}
// This pointer is active only during reflow of the ruby structure. It gets
// created when the corresponding ruby base container is reflowed, and it is
// destroyed when the ruby text container itself is reflowed.
mozilla::UniquePtr<nsLineLayout> mLineLayout;
explicit nsRubyTextContainerFrame(nsStyleContext* aContext)
: nsContainerFrame(aContext) {}
// 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.