mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
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:
parent
5f4cd8dd0b
commit
e464df4013
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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___ */
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user