/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ #include "nsLineLayout.h" #include "nsIStyleContext.h" #include "nsStyleConsts.h" #include "nsBlockFrame.h" #include "nsIContent.h" #include "nsIContentDelegate.h" #include "nsIPresContext.h" #include "nsISpaceManager.h" #include "nsIPtr.h" #include "nsAbsoluteFrame.h" #include "nsPlaceholderFrame.h" #include "nsCSSLayout.h" #include "nsCRT.h" #include "nsReflowCommand.h" #undef NOISY_REFLOW static NS_DEFINE_IID(kStyleDisplaySID, NS_STYLEDISPLAY_SID); static NS_DEFINE_IID(kStyleFontSID, NS_STYLEFONT_SID); static NS_DEFINE_IID(kStylePositionSID, NS_STYLEPOSITION_SID); static NS_DEFINE_IID(kStyleSpacingSID, NS_STYLESPACING_SID); static NS_DEFINE_IID(kStyleTextSID, NS_STYLETEXT_SID); NS_DEF_PTR(nsIContent); NS_DEF_PTR(nsIStyleContext); nsLineData::nsLineData() { mNextLine = nsnull; mPrevLine = nsnull; mFirstChild = nsnull; mChildCount = 0; mFirstContentOffset = 0; mLastContentOffset = 0; mLastContentIsComplete = PR_TRUE; mHasBullet = PR_FALSE; mIsBlock = PR_FALSE; mBounds.SetRect(0, 0, 0, 0); } nsLineData::~nsLineData() { } void nsLineData::UnlinkLine() { nsLineData* prevLine = mPrevLine; nsLineData* nextLine = mNextLine; if (nsnull != nextLine) nextLine->mPrevLine = prevLine; if (nsnull != prevLine) prevLine->mNextLine = nextLine; } nsresult nsLineData::Verify(PRBool aFinalCheck) const { NS_ASSERTION(mNextLine != this, "bad line linkage"); NS_ASSERTION(mPrevLine != this, "bad line linkage"); if (nsnull != mPrevLine) { NS_ASSERTION(mPrevLine->mNextLine == this, "bad line linkage"); } if (nsnull != mNextLine) { NS_ASSERTION(mNextLine->mPrevLine == this, "bad line linkage"); } if (aFinalCheck) { NS_ASSERTION(0 != mChildCount, "empty line"); NS_ASSERTION(nsnull != mFirstChild, "empty line"); nsIFrame* nextLinesFirstChild = nsnull; if (nsnull != mNextLine) { nextLinesFirstChild = mNextLine->mFirstChild; } // Check that number of children are ok and that the index in parent // information agrees with the content offsets. PRInt32 offset = mFirstContentOffset; PRInt32 len = 0; nsIFrame* child = mFirstChild; if (mHasBullet) { // Skip bullet child->GetNextSibling(child); len++; } while ((nsnull != child) && (child != nextLinesFirstChild)) { PRInt32 indexInParent; child->GetContentIndex(indexInParent); NS_ASSERTION(indexInParent == offset, "bad line offsets"); len++; if (len != mChildCount) { offset++; } child->GetNextSibling(child); } NS_ASSERTION(offset == mLastContentOffset, "bad mLastContentOffset"); NS_ASSERTION(len == mChildCount, "bad child count"); } // XXX verify content offsets and mLastContentIsComplete return NS_OK; } nsIFrame* nsLineData::GetLastChild() { nsIFrame* lastChild = mFirstChild; if (mChildCount > 1) { for (PRInt32 numKids = mChildCount - 1; --numKids >= 0; ) { nsIFrame* nextChild; lastChild->GetNextSibling(nextChild); lastChild = nextChild; } } return lastChild; } PRIntn nsLineData::GetLineNumber() const { PRIntn lineNumber = 0; nsLineData* prev = mPrevLine; while (nsnull != prev) { lineNumber++; prev = prev->mPrevLine; } return lineNumber; } void nsLineData::List(FILE* out, PRInt32 aIndent) const { // Indent for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out); // Output the first/last content offset fprintf(out, "line %d [%d,%d,%c] ", GetLineNumber(), mFirstContentOffset, mLastContentOffset, (mLastContentIsComplete ? 'T' : 'F')); // Output the bounds rect out << mBounds; // Output the children, one line at a time if (nsnull != mFirstChild) { fputs("<\n", out); aIndent++; nsIFrame* child = mFirstChild; for (PRInt32 numKids = mChildCount; --numKids >= 0; ) { child->List(out, aIndent); child->GetNextSibling(child); } aIndent--; for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out); fputs(">\n", out); } else { fputs("<>\n", out); } } //---------------------------------------------------------------------- nsLineLayout::nsLineLayout(nsBlockReflowState& aState) : mBlockReflowState(aState) { mBlock = aState.mBlock; mSpaceManager = aState.mSpaceManager; mBlock->GetContent(mBlockContent); mPresContext = aState.mPresContext; mUnconstrainedWidth = aState.mUnconstrainedWidth; mUnconstrainedHeight = aState.mUnconstrainedHeight; mMaxElementSizePointer = aState.mMaxElementSizePointer; mAscents = mAscentBuf; mMaxAscents = sizeof(mAscentBuf) / sizeof(mAscentBuf[0]); } nsLineLayout::~nsLineLayout() { NS_IF_RELEASE(mBlockContent); if (mAscents != mAscentBuf) { delete [] mAscents; } } nsresult nsLineLayout::Initialize(nsBlockReflowState& aState, nsLineData* aLine) { nsresult rv = NS_OK; mLine = aLine; mKidPrevInFlow = nsnull; mNewFrames = 0; mFramesReflowed = 0; mKidIndex = aLine->mFirstContentOffset; mReflowData.mMaxElementSize.width = 0; mReflowData.mMaxElementSize.height = 0; mReflowData.mMaxAscent = nsnull; mReflowData.mMaxDescent = nsnull; mMarginApplied = PR_FALSE; SetReflowSpace(aState.mCurrentBand.availSpace); mY = aState.mY; mMaxHeight = aState.mAvailSize.height; mReflowDataChanged = PR_FALSE; mLineHeight = 0; mAscentNum = 0; mKidFrame = nsnull; mPrevKidFrame = nsnull; mWordStart = nsnull; mWordStartParent = nsnull; mWordStartOffset = 0; mSkipLeadingWhiteSpace = PR_TRUE; mColumn = 0; return rv; } void nsLineLayout::SetReflowSpace(nsRect& aAvailableSpaceRect) { mReflowData.mX = aAvailableSpaceRect.x; mReflowData.mAvailWidth = aAvailableSpaceRect.width; mX0 = mReflowData.mX; mMaxWidth = mReflowData.mAvailWidth; mNewRightEdge = mX0 + mMaxWidth; mReflowDataChanged = PR_TRUE; } nsresult nsLineLayout::AddAscent(nscoord aAscent) { if (mAscentNum == mMaxAscents) { mMaxAscents *= 2; nscoord* newAscents = new nscoord[mMaxAscents]; if (nsnull != newAscents) { nsCRT::memcpy(newAscents, mAscents, sizeof(nscoord) * mAscentNum); if (mAscents != mAscentBuf) { delete [] mAscents; } mAscents = newAscents; } else { return NS_ERROR_OUT_OF_MEMORY; } } mAscents[mAscentNum++] = aAscent; return NS_OK; } nsIFrame* nsLineLayout::GetWordStartParent() { if (nsnull == mWordStartParent) { nsIFrame* frame = mWordStart; for (;;) { nsIFrame* parent; frame->GetGeometricParent(parent); if (nsnull == parent) { break; } if (mBlock == parent) { break; } frame = parent; } mWordStartParent = frame; } return mWordStartParent; } nsresult nsLineLayout::WordBreakReflow() { // Restore line layout state to just before the word start. mReflowData = mWordStartReflowData; // Walk up from the frame that contains the start of the word to the // child of the block that contains the word. nsIFrame* frame = GetWordStartParent(); // Compute the available space to reflow the child. Note that since // we are reflowing this child for the second time we know that the // child will fit before we begin. nsresult rv; nsSize kidAvailSize; kidAvailSize.width = mReflowData.mAvailWidth; kidAvailSize.height = mMaxHeight; if (!mUnconstrainedWidth) { nsIStyleContextPtr kidSC; rv = frame->GetStyleContext(mPresContext, kidSC.AssignRef()); if (NS_OK != rv) { return rv; } nsStyleSpacing* kidSpacing = (nsStyleSpacing*) kidSC->GetData(kStyleSpacingSID); nsMargin kidMargin; kidSpacing->CalcMarginFor(frame, kidMargin); kidAvailSize.width -= kidMargin.left + kidMargin.right; } // Reflow that child of the block having set the reflow type so that // the child knows whats going on. mReflowType = NS_LINE_LAYOUT_REFLOW_TYPE_WORD_WRAP; mReflowResult = NS_LINE_LAYOUT_REFLOW_RESULT_NOT_AWARE; nsSize maxElementSize; nsReflowMetrics kidSize; nsReflowStatus kidReflowStatus; nsSize* kidMaxElementSize = nsnull; if (nsnull != mMaxElementSizePointer) { kidMaxElementSize = &maxElementSize; } rv = mBlock->ReflowInlineChild(frame, mPresContext, kidSize, kidAvailSize, kidMaxElementSize, kidReflowStatus); return rv; } /** * Attempt to avoid reflowing a child by seeing if it's been touched * since the last time it was reflowed. */ nsresult nsLineLayout::ReflowMappedChild(nsReflowCommand* aReflowCommand) { // XXX look at reflow command and look at the next frame to get the // command and if it's mKidFrame then pass through to ReflowChild // otherwise don't if (nsnull != aReflowCommand) { return ReflowChild(aReflowCommand); } NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: attempt frame=%p[%d]", mKidFrame, mKidIndex)); // If the child is a container then we need to reflow it if there is // a change in width. Note that if it's an empty container then it // doesn't really matter how much space we give it. if (mBlockReflowState.mDeltaWidth != 0) { nsIFrame* f; mKidFrame->FirstChild(f); if (nsnull != f) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: has children")); return ReflowChild(aReflowCommand); } } // If we need the max-element size and we are splittable then we // have to reflow to get it. nsIFrame::SplittableType splits; mKidFrame->IsSplittable(splits); if (nsnull != mMaxElementSizePointer) { if (nsIFrame::NotSplittable != splits) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: need max-element-size")); return ReflowChild(aReflowCommand); } } nsFrameState state; mKidFrame->GetFrameState(state); // XXX a better term for this is "dirty" and once we add a dirty // bit that's what we'll do here. // XXX this check will cause pass2 of table reflow to reflow // everything; tables will be even faster if we have a dirty bit // instead (that way we can avoid reflowing non-splittables on // pass2) if (0 != (state & NS_FRAME_IN_REFLOW)) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: frame is dirty")); return ReflowChild(aReflowCommand); } if (nsIFrame::NotSplittable != splits) { // XXX a next-in-flow propogated dirty-bit eliminates this code // The splittable frame has not yet been reflowed. This means // that, in theory, its state is well defined. However, if it has // a prev-in-flow and that frame has been touched then we need to // reflow this frame. nsIFrame* prevInFlow; mKidFrame->GetPrevInFlow(prevInFlow); if (nsnull != prevInFlow) { nsFrameState prevState; prevInFlow->GetFrameState(prevState); if (0 != (prevState & NS_FRAME_IN_REFLOW)) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: prev-in-flow frame is dirty")); return ReflowChild(aReflowCommand); } } // If the child has a next-in-flow then never-mind, we need to // reflow it in case it has more/less space to reflow into. nsIFrame* nextInFlow; mKidFrame->GetNextInFlow(nextInFlow); if (nsnull != nextInFlow) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: frame has next-in-flow")); return ReflowChild(aReflowCommand); } } // Success! We have (so far) avoided reflowing the child. However, // we do need to place it and advance our position state. Get the // size of the child and its reflow metrics for placing. nsIStyleContextPtr kidSC; nsresult rv = mKidFrame->GetStyleContext(mPresContext, kidSC.AssignRef()); if (NS_OK != rv) { return rv; } nsStyleSpacing* kidSpacing = (nsStyleSpacing*) kidSC->GetData(kStyleSpacingSID); nsStyleDisplay* kidDisplay = (nsStyleDisplay*) kidSC->GetData(kStyleDisplaySID); PRBool isBlock = PR_FALSE; switch (kidDisplay->mDisplay) { case NS_STYLE_DISPLAY_BLOCK: case NS_STYLE_DISPLAY_LIST_ITEM: if (mKidFrame != mLine->mFirstChild) { // Block items must be at the start of a line, therefore we need // to break before the block item. NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: block requires break-before")); return NS_LINE_LAYOUT_BREAK_BEFORE; } isBlock = PR_TRUE; break; } // Compute new total width of child using its current margin values // (they may have changed since the last time the child was reflowed) nsRect kidRect; mKidFrame->GetRect(kidRect); nsMargin kidMargin; kidSpacing->CalcMarginFor(mKidFrame, kidMargin); nscoord totalWidth; totalWidth = kidMargin.left + kidMargin.right + kidRect.width; // If the child intersects the area affected by the reflow then // we need to reflow it. if (mReflowData.mX + kidMargin.left + kidRect.width > mNewRightEdge) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: failed edge test")); // XXX if !splittable then return NS_LINE_LAYOUT_BREAK_BEFORE return ReflowChild(aReflowCommand); } // Make sure the child will fit. The child always fits if it's the // first child on the line. if (mUnconstrainedWidth || (mKidFrame == mLine->mFirstChild) || (totalWidth <= mReflowData.mAvailWidth)) { // By convention, mReflowResult is set during ResizeReflow, // IncrementalReflow AND GetReflowMetrics by those frames that are // line layout aware. mReflowResult = NS_LINE_LAYOUT_REFLOW_RESULT_NOT_AWARE; nsReflowMetrics kidMetrics; mKidFrame->GetReflowMetrics(mPresContext, kidMetrics); nsSize maxElementSize; nsSize* kidMaxElementSize = nsnull; if (nsnull != mMaxElementSizePointer) { kidMaxElementSize = &maxElementSize; maxElementSize.width = kidRect.width; maxElementSize.height = kidRect.height; } kidRect.x = mReflowData.mX + kidMargin.left; kidRect.y = mY; if (NS_LINE_LAYOUT_REFLOW_RESULT_NOT_AWARE == mReflowResult) { mSkipLeadingWhiteSpace = PR_FALSE; } NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: fit size=%d,%d", kidRect.width, kidRect.height)); mLine->mIsBlock = isBlock; return PlaceChild(kidRect, kidMetrics, kidMaxElementSize, kidMargin, NS_FRAME_COMPLETE); } // The child doesn't fit as is; if it's splittable then reflow it // otherwise return break-before status so that the non-splittable // child is pushed to the next line. if (nsIFrame::NotSplittable != splits) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: can't directly fit")); return ReflowChild(aReflowCommand); } return NS_LINE_LAYOUT_BREAK_BEFORE; } // Return values: <0 for error // 0 == NS_LINE_LAYOUT nsresult nsLineLayout::ReflowChild(nsReflowCommand* aReflowCommand) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowChild: attempt frame=%p[%d] mX=%d mAvailWidth=%d", mKidFrame, mKidIndex, mReflowData.mX, mReflowData.mAvailWidth)); // Get kid frame's style context nsIStyleContextPtr kidSC; nsresult rv = mKidFrame->GetStyleContext(mPresContext, kidSC.AssignRef()); if (NS_OK != rv) { return rv; } // See if frame belongs in the line. // XXX absolute positioning // XXX floating frames // XXX break-before nsStyleDisplay * kidDisplay = (nsStyleDisplay*) kidSC->GetData(kStyleDisplaySID); PRBool isBlock = PR_FALSE; PRBool isFirstChild = PRBool(mKidFrame == mLine->mFirstChild); switch (kidDisplay->mDisplay) { case NS_STYLE_DISPLAY_NONE: // Make sure the frame remains zero sized. mKidFrame->WillReflow(*mPresContext); mKidFrame->SetRect(nsRect(mReflowData.mX, mY, 0, 0)); NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowChild: display=none")); return NS_LINE_LAYOUT_COMPLETE; case NS_STYLE_DISPLAY_INLINE: break; default: isBlock = PR_TRUE; if (!isFirstChild) { // XXX Make sure child is dirty for next time mKidFrame->WillReflow(*mPresContext); NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowChild: block requires break-before")); return NS_LINE_LAYOUT_BREAK_BEFORE; } break; } // Get the available size to reflow the child into nsSize kidAvailSize; kidAvailSize.width = mReflowData.mAvailWidth; kidAvailSize.height = mMaxHeight; nsStyleSpacing* kidSpacing = (nsStyleSpacing*) kidSC->GetData(kStyleSpacingSID); nsMargin kidMargin; kidSpacing->CalcMarginFor(mKidFrame, kidMargin); if (!mUnconstrainedWidth) { kidAvailSize.width -= kidMargin.left + kidMargin.right; if (!isFirstChild && (kidAvailSize.width <= 0)) { // No room. // XXX Make sure child is dirty for next time mKidFrame->WillReflow(*mPresContext); NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowChild: !fit")); return NS_LINE_LAYOUT_BREAK_BEFORE; } } // Reflow the child mFramesReflowed++; nsRect kidRect; nsSize maxElementSize; nsReflowMetrics kidMetrics; nsSize* kidMaxElementSize = nsnull; nsReflowStatus kidReflowStatus; if (nsnull != mMaxElementSizePointer) { kidMaxElementSize = &maxElementSize; } mReflowResult = NS_LINE_LAYOUT_REFLOW_RESULT_NOT_AWARE; nscoord dx = mReflowData.mX + kidMargin.left; NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowChild: reflowing frame=%p[%d] into %d,%d", mKidFrame, mKidIndex, kidAvailSize.width, kidAvailSize.height)); if (aReflowCommand) { nsIFrame* nextFrame; mSpaceManager->Translate(dx, mY); kidReflowStatus = aReflowCommand->Next(mSpaceManager, kidRect, kidAvailSize, nextFrame); mSpaceManager->Translate(-dx, -mY); kidRect.x = dx; kidRect.y = mY; kidMetrics.width = kidRect.width; kidMetrics.height = kidRect.height; kidMetrics.ascent = kidRect.height; kidMetrics.descent = 0; } else if (isBlock) { // Calculate top margin by collapsing with previous bottom margin nscoord negTopMargin; nscoord posTopMargin; nsMargin kidMargin; kidSpacing->CalcMarginFor(mKidFrame, kidMargin); if (kidMargin.top < 0) { negTopMargin = -kidMargin.top; posTopMargin = 0; } else { negTopMargin = 0; posTopMargin = kidMargin.top; } nscoord maxPos = PR_MAX(mBlockReflowState.mPrevPosBottomMargin, posTopMargin); nscoord maxNeg = PR_MAX(mBlockReflowState.mPrevNegBottomMargin, negTopMargin); nscoord topMargin = maxPos - maxNeg; // Save away bottom margin information for later if (kidMargin.bottom < 0) { mBlockReflowState.mPrevNegBottomMargin = -kidMargin.bottom; mBlockReflowState.mPrevPosBottomMargin = 0; } else { mBlockReflowState.mPrevNegBottomMargin = 0; mBlockReflowState.mPrevPosBottomMargin = kidMargin.bottom; } mY += topMargin; mBlockReflowState.mY += topMargin; // XXX tell block what topMargin ended up being so that it can // undo it if it ends up pushing the line. mSpaceManager->Translate(dx, mY); mKidFrame->WillReflow(*mPresContext); rv = mBlock->ReflowBlockChild(mKidFrame, mPresContext, mSpaceManager, kidAvailSize, kidRect, kidMaxElementSize, kidReflowStatus); mSpaceManager->Translate(-dx, -mY); kidRect.x = dx; kidRect.y = mY; kidMetrics.width = kidRect.width; kidMetrics.height = kidRect.height; kidMetrics.ascent = kidRect.height; kidMetrics.descent = 0; } else { // Reflow the inline child mKidFrame->WillReflow(*mPresContext); rv = mBlock->ReflowInlineChild(mKidFrame, mPresContext, kidMetrics, kidAvailSize, kidMaxElementSize, kidReflowStatus); // After we reflow the inline child we will know whether or not it // has any height/width. If it doesn't have any height/width then // we do not yet apply any previous block bottom margin. if ((0 != kidMetrics.height) && !mMarginApplied) { // Before we place the first inline child on this line apply // the previous block's bottom margin. nscoord bottomMargin = mBlockReflowState.mPrevPosBottomMargin - mBlockReflowState.mPrevNegBottomMargin; mY += bottomMargin; mBlockReflowState.mY += bottomMargin; // XXX tell block what bottomMargin ended up being so that it can // undo it if it ends up pushing the line. mMarginApplied = PR_TRUE; mBlockReflowState.mPrevPosBottomMargin = 0; mBlockReflowState.mPrevNegBottomMargin = 0; } kidRect.x = dx; kidRect.y = mY; kidRect.width = kidMetrics.width; kidRect.height = kidMetrics.height; } if (NS_OK != rv) return rv; // See if the child fit if (kidMetrics.width > kidAvailSize.width) { // The child took up too much space. This condition is ignored if // the child is the first child (by definition the first child // always fits) or we have a word start and the word start is the // first child. if (!isFirstChild) { // It's not our first child. if (nsnull != mWordStart) { // We have a word to break at if (GetWordStartParent() != mLine->mFirstChild) { // The word is not our first child WordBreakReflow(); // XXX mKidPrevInFlow return NS_LINE_LAYOUT_BREAK_BEFORE; } } else { // There is no word to break at and it's not our first child. // We are out of room. // XXX mKidPrevInFlow NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowChild: !fit size=%d,%d", kidRect.width, kidRect.height)); return NS_LINE_LAYOUT_BREAK_BEFORE; } } } // For non-aware children they act like words which means that space // immediately following them must not be skipped over. if (NS_LINE_LAYOUT_REFLOW_RESULT_NOT_AWARE == mReflowResult) { mSkipLeadingWhiteSpace = PR_FALSE; } // Now place the child NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowChild: fit size=%d,%d", kidRect.width, kidRect.height)); mLine->mIsBlock = isBlock; return PlaceChild(kidRect, kidMetrics, kidMaxElementSize, kidMargin, kidReflowStatus); } nsresult nsLineLayout::PlaceChild(const nsRect& kidRect, const nsReflowMetrics& kidMetrics, const nsSize* kidMaxElementSize, const nsMargin& kidMargin, nsReflowStatus kidReflowStatus) { // Place child mKidFrame->SetRect(kidRect); // Advance // XXX RTL nscoord horizontalMargins = kidMargin.left + kidMargin.right; nscoord totalWidth = kidMetrics.width + horizontalMargins; mReflowData.mX += totalWidth; if (!mUnconstrainedWidth) { mReflowData.mAvailWidth -= totalWidth; } if (nsnull != mMaxElementSizePointer) { // XXX I'm not certain that this is doing the right thing; rethink this nscoord elementWidth = kidMaxElementSize->width + horizontalMargins; if (elementWidth > mReflowData.mMaxElementSize.width) { mReflowData.mMaxElementSize.width = elementWidth; } if (kidMetrics.height > mReflowData.mMaxElementSize.height) { mReflowData.mMaxElementSize.height = kidMetrics.height; } } if (kidMetrics.ascent > mReflowData.mMaxAscent) { mReflowData.mMaxAscent = kidMetrics.ascent; } if (kidMetrics.descent > mReflowData.mMaxDescent) { mReflowData.mMaxDescent = kidMetrics.descent; } AddAscent(mLine->mIsBlock ? 0 : kidMetrics.ascent); // Set completion status nsresult rv = NS_LINE_LAYOUT_COMPLETE; mLine->mLastContentOffset = mKidIndex; if (NS_FRAME_IS_COMPLETE(kidReflowStatus)) { mLine->mLastContentIsComplete = PR_TRUE; if (mLine->mIsBlock || (NS_LINE_LAYOUT_REFLOW_RESULT_BREAK_AFTER == mReflowResult)) { rv = NS_LINE_LAYOUT_BREAK_AFTER; } mKidPrevInFlow = nsnull; } else { mLine->mLastContentIsComplete = PR_FALSE; rv = NS_LINE_LAYOUT_NOT_COMPLETE; mKidPrevInFlow = mKidFrame; } NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::PlaceChild: rv=%d", rv)); return rv; } nsresult nsLineLayout::IncrementalReflowFromChild(nsReflowCommand& aReflowCommand, nsIFrame* aChildFrame) { #if 0 // Get the current bounds. We'll need this to adjust the frames that follow nsRect oldBounds; aChildFrame->GetRect(oldBounds); #endif // For the time being reflow all the children, and when we get to aChildFrame // handle it specially nsresult reflowStatus = NS_LINE_LAYOUT_COMPLETE; mLine->mBounds.x = mReflowData.mX; mLine->mBounds.y = mY; mKidFrame = mLine->mFirstChild; PRInt32 kidNum = 0; while (kidNum < mLine->mChildCount) { // XXX Code to avoid reflowing a child goes here nsresult childReflowStatus; if (mKidFrame == aChildFrame) { childReflowStatus = ReflowChild(&aReflowCommand); } else { childReflowStatus = ReflowChild(nsnull); } if (childReflowStatus < 0) { reflowStatus = childReflowStatus; goto done; } switch (childReflowStatus) { default: case NS_LINE_LAYOUT_COMPLETE: mPrevKidFrame = mKidFrame; mKidFrame->GetNextSibling(mKidFrame); mKidIndex++; kidNum++; break; case NS_LINE_LAYOUT_NOT_COMPLETE: reflowStatus = childReflowStatus; mPrevKidFrame = mKidFrame; mKidFrame->GetNextSibling(mKidFrame); kidNum++; goto split_line; case NS_LINE_LAYOUT_BREAK_BEFORE: reflowStatus = childReflowStatus; goto split_line; case NS_LINE_LAYOUT_BREAK_AFTER: reflowStatus = childReflowStatus; mPrevKidFrame = mKidFrame; mKidFrame->GetNextSibling(mKidFrame); mKidIndex++; kidNum++; split_line: reflowStatus = SplitLine(childReflowStatus, mLine->mChildCount - kidNum); goto done; } } done: // Perform alignment operations if (mLine->mIsBlock) { mLineHeight = mReflowData.mMaxAscent + mReflowData.mMaxDescent; } else { AlignChildren(); } // Set final bounds of the line mLine->mBounds.height = mLineHeight; mLine->mBounds.width = mReflowData.mX - mLine->mBounds.x; NS_ASSERTION(((reflowStatus < 0) || (reflowStatus == NS_LINE_LAYOUT_COMPLETE) || (reflowStatus == NS_LINE_LAYOUT_NOT_COMPLETE) || (reflowStatus == NS_LINE_LAYOUT_BREAK_BEFORE) || (reflowStatus == NS_LINE_LAYOUT_BREAK_AFTER)), "bad return status from ReflowMapped"); return reflowStatus; } //---------------------------------------------------------------------- nsresult nsLineLayout::SplitLine(PRInt32 aChildReflowStatus, PRInt32 aRemainingKids) { nsresult rv = NS_LINE_LAYOUT_COMPLETE; if (NS_LINE_LAYOUT_NOT_COMPLETE == aChildReflowStatus) { // When a line is not complete it indicates that the last child on // the line reflowed and took some space but wasn't given enough // space to complete. Sometimes when this happens we will need to // create a next-in-flow for the child. nsIFrame* nextInFlow; mPrevKidFrame->GetNextInFlow(nextInFlow); if (nsnull == nextInFlow) { // Create a continuation frame for the child frame and insert it // into our lines child list. nsIFrame* nextFrame; mPrevKidFrame->GetNextSibling(nextFrame); nsIStyleContext* kidSC; mPrevKidFrame->GetStyleContext(mPresContext, kidSC); mPrevKidFrame->CreateContinuingFrame(mPresContext, mBlock, kidSC, nextInFlow); NS_RELEASE(kidSC); if (nsnull == nextInFlow) { return NS_ERROR_OUT_OF_MEMORY; } mPrevKidFrame->SetNextSibling(nextInFlow); nextInFlow->SetNextSibling(nextFrame); mNewFrames++; // Add new child to our line mLine->mChildCount++; // Set mKidFrame to the new next-in-flow so that we will // push it when we push children. Increment the number of // remaining kids now that there is one more. mKidFrame = nextInFlow; aRemainingKids++; } } if (0 != aRemainingKids) { NS_ASSERTION(nsnull != mKidFrame, "whoops"); nsLineData* from = mLine; nsLineData* to = mLine->mNextLine; if (nsnull != to) { // Only push into the next line if it's empty; otherwise we can // end up pushing a frame which is continued into the same frame // as it's continuation. This causes all sorts of side effects // so we don't allow it. if (to->mChildCount != 0) { nsLineData* insertedLine = new nsLineData(); from->mNextLine = insertedLine; to->mPrevLine = insertedLine; insertedLine->mPrevLine = from; insertedLine->mNextLine = to; to = insertedLine; to->mLastContentOffset = from->mLastContentOffset; to->mLastContentIsComplete = from->mLastContentIsComplete; } } else { to = new nsLineData(); to->mPrevLine = from; from->mNextLine = to; } if (nsnull == to) { return NS_ERROR_OUT_OF_MEMORY; } PRInt32 kidIndexInParent; mKidFrame->GetContentIndex(kidIndexInParent); to->mFirstChild = mKidFrame; to->mChildCount += aRemainingKids; to->mFirstContentOffset = kidIndexInParent; // The to-line is going to be reflowed therefore it's last content // offset and completion status don't matter. In fact, it's expensive // to compute them so don't bother. #ifdef NS_DEBUG to->mLastContentOffset = -1; to->mLastContentIsComplete = PRPackedBool(0x255); #endif from->mChildCount -= aRemainingKids; NS_ASSERTION(0 != from->mChildCount, "bad push"); #ifdef NS_DEBUG if (nsIFrame::GetVerifyTreeEnable()) { from->Verify(); } #endif #ifdef NOISY_REFLOW printf("After push, from-line (%d):\n", aRemainingKids); from->List(stdout, 1); printf("After push, to-line:\n"); to->List(stdout, 1); #endif } return aChildReflowStatus; } //---------------------------------------------------------------------- nsresult nsLineLayout::ReflowMapped() { nsresult reflowStatus = NS_LINE_LAYOUT_COMPLETE; mKidFrame = mLine->mFirstChild; PRInt32 kidNum = 0; while (kidNum < mLine->mChildCount) { nsresult childReflowStatus = ReflowMappedChild(nsnull); if (childReflowStatus < 0) { reflowStatus = childReflowStatus; goto done; } switch (childReflowStatus) { default: case NS_LINE_LAYOUT_COMPLETE: mPrevKidFrame = mKidFrame; mKidFrame->GetNextSibling(mKidFrame); mKidIndex++; kidNum++; break; case NS_LINE_LAYOUT_NOT_COMPLETE: reflowStatus = childReflowStatus; mPrevKidFrame = mKidFrame; mKidFrame->GetNextSibling(mKidFrame); kidNum++; goto split_line; case NS_LINE_LAYOUT_BREAK_BEFORE: reflowStatus = childReflowStatus; goto split_line; case NS_LINE_LAYOUT_BREAK_AFTER: reflowStatus = childReflowStatus; mPrevKidFrame = mKidFrame; mKidFrame->GetNextSibling(mKidFrame); mKidIndex++; kidNum++; split_line: reflowStatus = SplitLine(childReflowStatus, mLine->mChildCount - kidNum); goto done; } } done: NS_ASSERTION(((reflowStatus < 0) || (reflowStatus == NS_LINE_LAYOUT_COMPLETE) || (reflowStatus == NS_LINE_LAYOUT_NOT_COMPLETE) || (reflowStatus == NS_LINE_LAYOUT_BREAK_BEFORE) || (reflowStatus == NS_LINE_LAYOUT_BREAK_AFTER)), "bad return status from ReflowMapped"); return reflowStatus; } //---------------------------------------------------------------------- static PRBool IsBlock(nsStyleDisplay* aDisplay) { switch (aDisplay->mDisplay) { case NS_STYLE_DISPLAY_BLOCK: case NS_STYLE_DISPLAY_LIST_ITEM: return PR_TRUE; } return PR_FALSE; } // XXX fix this code to look at the available width and if it's too // small for the next child then skip the pullup (and return // BREAK_AFTER status). nsresult nsLineLayout::PullUpChildren() { nsresult reflowStatus = NS_LINE_LAYOUT_COMPLETE; nsIFrame* prevKidFrame = mPrevKidFrame; nsBlockFrame* currentBlock = mBlock; nsLineData* line = mLine->mNextLine; while (nsnull != currentBlock) { // Pull children from the next line while (nsnull != line) { // Get first child from next line mKidFrame = line->mFirstChild; if (nsnull == mKidFrame) { NS_ASSERTION(0 == line->mChildCount, "bad line list"); nsLineData* nextLine = line->mNextLine; nsLineData* prevLine = line->mPrevLine; if (nsnull != prevLine) prevLine->mNextLine = nextLine; if (nsnull != nextLine) nextLine->mPrevLine = prevLine; delete line;/* XXX free-list in block-reflow-state? */ line = nextLine; continue; } // XXX Avoid the pullup work if the child cannot already fit // (e.g. it's not splittable and can't fit) // If the child is a block element then if this is not the first // line in the block or if it's the first line and it's not the // first child in the line then we cannot pull-up the child. nsresult rv; nsIStyleContextPtr kidSC; rv = mKidFrame->GetStyleContext(mPresContext, kidSC.AssignRef()); if (NS_OK != rv) { return rv; } nsStyleDisplay* kidDisplay = (nsStyleDisplay*) kidSC->GetData(kStyleDisplaySID); if (IsBlock(kidDisplay)) { if ((nsnull != mLine->mPrevLine) || (0 != mLine->mChildCount)) { goto done; } } // Make pulled child part of this line NS_FRAME_LOG(NS_FRAME_TRACE_PUSH_PULL, ("nsLineLayout::PullUpChildren: trying to pull frame=%p", mKidFrame)); mLine->mChildCount++; if (0 == --line->mChildCount) { // Remove empty lines from the list nsLineData* nextLine = line->mNextLine; nsLineData* prevLine = line->mPrevLine; if (nsnull != prevLine) prevLine->mNextLine = nextLine; if (nsnull != nextLine) nextLine->mPrevLine = prevLine; delete line;/* XXX free-list in block-reflow-state? */ line = nextLine; } else { // Repair the first content offset of the line. The first // child of the line's index-in-parent should be the line's // new first content offset. mKidFrame->GetNextSibling(line->mFirstChild); PRInt32 indexInParent; line->mFirstChild->GetContentIndex(indexInParent); line->mFirstContentOffset = indexInParent; #ifdef NS_DEBUG if (nsIFrame::GetVerifyTreeEnable()) { line->Verify(); } #endif } // Try to reflow it like any other mapped child nsresult childReflowStatus = ReflowMappedChild(nsnull); if (childReflowStatus < 0) { reflowStatus = childReflowStatus; goto done; } PRInt32 pushCount; switch (childReflowStatus) { default: case NS_LINE_LAYOUT_COMPLETE: mPrevKidFrame = mKidFrame; mKidFrame = nsnull; mKidIndex++; break; case NS_LINE_LAYOUT_NOT_COMPLETE: reflowStatus = childReflowStatus; mPrevKidFrame = mKidFrame; mKidFrame = nsnull; pushCount = 0; goto split_line; case NS_LINE_LAYOUT_BREAK_BEFORE: reflowStatus = childReflowStatus; pushCount = 1; goto split_line; case NS_LINE_LAYOUT_BREAK_AFTER: reflowStatus = childReflowStatus; mPrevKidFrame = mKidFrame; mKidFrame = nsnull; mKidIndex++; pushCount = 0; split_line: reflowStatus = SplitLine(childReflowStatus, pushCount); goto done; } } // Grab the block's next in flow nsIFrame* nextInFlow; currentBlock->GetNextInFlow(nextInFlow); currentBlock = (nsBlockFrame*)nextInFlow; if (nsnull != currentBlock) { line = currentBlock->GetFirstLine(); } } done: NS_ASSERTION(((reflowStatus < 0) || (reflowStatus == NS_LINE_LAYOUT_COMPLETE) || (reflowStatus == NS_LINE_LAYOUT_NOT_COMPLETE) || (reflowStatus == NS_LINE_LAYOUT_BREAK_BEFORE) || (reflowStatus == NS_LINE_LAYOUT_BREAK_AFTER)), "bad return status from PullUpChildren"); return reflowStatus; } //---------------------------------------------------------------------- nsresult nsLineLayout::CreateFrameFor(nsIContent* aKid) { nsIStyleContextPtr kidSC = mPresContext->ResolveStyleContextFor(aKid, mBlock); // XXX bad API if (nsnull == kidSC) { return NS_ERROR_OUT_OF_MEMORY; } nsStylePosition* kidPosition = (nsStylePosition*) kidSC->GetData(kStylePositionSID); nsStyleDisplay* kidDisplay = (nsStyleDisplay*) kidSC->GetData(kStyleDisplaySID); // Check whether it wants to floated or absolutely positioned PRBool isBlock = PR_FALSE; nsIFrame* kidFrame; nsresult rv; if (NS_STYLE_POSITION_ABSOLUTE == kidPosition->mPosition) { rv = AbsoluteFrame::NewFrame(&kidFrame, aKid, mBlock); if (NS_OK == rv) { kidFrame->SetStyleContext(mPresContext, kidSC); } } else if (kidDisplay->mFloats != NS_STYLE_FLOAT_NONE) { rv = PlaceholderFrame::NewFrame(&kidFrame, aKid, mBlock); if (NS_OK == rv) { kidFrame->SetStyleContext(mPresContext, kidSC); } } else if (nsnull == mKidPrevInFlow) { // Create initial frame for the child nsIContentDelegate* kidDel; switch (kidDisplay->mDisplay) { case NS_STYLE_DISPLAY_NONE: rv = nsFrame::NewFrame(&kidFrame, aKid, mBlock); if (NS_OK == rv) { kidFrame->SetStyleContext(mPresContext, kidSC); } break; case NS_STYLE_DISPLAY_BLOCK: case NS_STYLE_DISPLAY_LIST_ITEM: isBlock = PR_TRUE; // FALL THROUGH default: kidDel = aKid->GetDelegate(mPresContext); rv = kidDel->CreateFrame(mPresContext, aKid, mBlock, kidSC, kidFrame); NS_RELEASE(kidDel); break; } } else { // Since kid has a prev-in-flow, use that to create the next // frame. rv = mKidPrevInFlow->CreateContinuingFrame(mPresContext, mBlock, kidSC, kidFrame); NS_ASSERTION(0 == mLine->mChildCount, "bad continuation"); } if (NS_OK != rv) { return rv; } mKidFrame = kidFrame; mNewFrames++; if (isBlock && (0 != mLine->mChildCount)) { // When we are not at the start of a line we need to break // before a block element. return NS_LINE_LAYOUT_BREAK_BEFORE; } return rv; } nsresult nsLineLayout::ReflowUnmapped() { nsresult reflowStatus = NS_LINE_LAYOUT_COMPLETE; for (;;) { nsIContentPtr kid = mBlockContent->ChildAt(mKidIndex); if (kid.IsNull()) { break; } // Create a frame for the new content nsresult rv = CreateFrameFor(kid); if (rv < 0) { reflowStatus = rv; goto done; } // Add frame to our list if (nsnull != mPrevKidFrame) { mPrevKidFrame->SetNextSibling(mKidFrame); } if (0 == mLine->mChildCount) { mLine->mFirstChild = mKidFrame; } mLine->mChildCount++; nsresult childReflowStatus; PRInt32 pushCount; if (rv == NS_LINE_LAYOUT_BREAK_BEFORE) { // If we break before a frame is even supposed to layout then we // need to split the line. childReflowStatus = rv; pushCount = 1; // XXX Mark new frame dirty so it gets reflow later on mKidFrame->WillReflow(*mPresContext); goto split_line; } // Reflow new child frame childReflowStatus = ReflowChild(nsnull); if (childReflowStatus < 0) { reflowStatus = childReflowStatus; goto done; } switch (childReflowStatus) { default: case NS_LINE_LAYOUT_COMPLETE: mPrevKidFrame = mKidFrame; mKidFrame = nsnull; mKidIndex++; break; case NS_LINE_LAYOUT_NOT_COMPLETE: reflowStatus = childReflowStatus; mPrevKidFrame = mKidFrame; mKidFrame = nsnull; pushCount = 0; goto split_line; case NS_LINE_LAYOUT_BREAK_BEFORE: reflowStatus = childReflowStatus; pushCount = 1; goto split_line; case NS_LINE_LAYOUT_BREAK_AFTER: reflowStatus = childReflowStatus; mPrevKidFrame = mKidFrame; mKidFrame = nsnull; mKidIndex++; pushCount = 0; split_line: reflowStatus = SplitLine(childReflowStatus, pushCount); goto done; } } NS_ASSERTION(nsnull == mLine->mNextLine, "bad line list"); done: NS_ASSERTION(((reflowStatus < 0) || (reflowStatus == NS_LINE_LAYOUT_COMPLETE) || (reflowStatus == NS_LINE_LAYOUT_NOT_COMPLETE) || (reflowStatus == NS_LINE_LAYOUT_BREAK_BEFORE) || (reflowStatus == NS_LINE_LAYOUT_BREAK_AFTER)), "bad return status from ReflowUnmapped"); return reflowStatus; } //---------------------------------------------------------------------- nsresult nsLineLayout::ReflowLine() { NS_FRAME_LOG(NS_FRAME_TRACE_CALLS, ("enter nsLineLayout::ReflowLine: childCount=%d", mLine->mChildCount)); nsresult rv = NS_LINE_LAYOUT_COMPLETE; mLine->mBounds.x = mReflowData.mX; mLine->mBounds.y = mY; // Reflow the mapped frames if (0 != mLine->mChildCount) { rv = ReflowMapped(); if (rv < 0) return rv; } // Pull-up any frames from the next line if (NS_LINE_LAYOUT_COMPLETE == rv) { if (nsnull != mLine->mNextLine) { rv = PullUpChildren(); if (rv < 0) return rv; } // Try reflowing any unmapped children if (NS_LINE_LAYOUT_COMPLETE == rv) { if (nsnull == mLine->mNextLine) { rv = ReflowUnmapped(); if (rv < 0) return rv; } } } // Perform alignment operations if (mLine->mIsBlock) { mLineHeight = mReflowData.mMaxAscent + mReflowData.mMaxDescent; } else { if (0 != mFramesReflowed) { AlignChildren(); } } // Set final bounds of the line mLine->mBounds.height = mLineHeight; mLine->mBounds.width = mReflowData.mX - mLine->mBounds.x; #ifdef NS_DEBUG if (nsIFrame::GetVerifyTreeEnable()) { mLine->Verify(); } #endif NS_FRAME_LOG(NS_FRAME_TRACE_CALLS, ("exit nsLineLayout::ReflowLine: childCount=%d", mLine->mChildCount)); return rv; } nsresult nsLineLayout::AlignChildren() { NS_PRECONDITION(mLine->mChildCount == mAscentNum, "bad line reflow"); nsresult rv = NS_OK; nsIStyleContextPtr blockSC; mBlock->GetStyleContext(mPresContext, blockSC.AssignRef()); nsStyleFont* blockFont = (nsStyleFont*) blockSC->GetData(kStyleFontSID); nsStyleText* blockText = (nsStyleText*) blockSC->GetData(kStyleTextSID); // First vertically align the children on the line; this will // compute the actual line height for us. mLineHeight = nsCSSLayout::VerticallyAlignChildren(mPresContext, mBlock, blockFont, mY, mLine->mFirstChild, mLine->mChildCount, mAscents, mReflowData.mMaxAscent); // Now horizontally place the children nsCSSLayout::HorizontallyPlaceChildren(mPresContext, mBlock, blockText, mLine->mFirstChild, mLine->mChildCount, mReflowData.mX - mX0, mMaxWidth); // Last, apply relative positioning nsCSSLayout::RelativePositionChildren(mPresContext, mBlock, mLine->mFirstChild, mLine->mChildCount); return rv; }