/* -*- 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" #include "nsIFontMetrics.h" #include "nsHTMLFrame.h" #include "nsScrollFrame.h" #undef NOISY_REFLOW // XXX zap mAscentNum 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; mIsBlock = PR_FALSE; mBounds.SetRect(0, 0, 0, 0); mFloaters = nsnull; } nsLineData::~nsLineData() { delete mFloaters; } 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; 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"); } if (1 == mChildCount) { if (mIsBlock) { nsIFrame* child = mFirstChild; nsIStyleContext* sc; child->GetStyleContext(nsnull, sc); const nsStyleDisplay* display = (const nsStyleDisplay*) sc->GetStyleData(eStyleStruct_Display); NS_ASSERTION((NS_STYLE_DISPLAY_BLOCK == display->mDisplay) || (NS_STYLE_DISPLAY_LIST_ITEM == display->mDisplay), "bad mIsBlock state"); } } // 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; SetReflowSpace(aState.mCurrentBand.availSpace); mState.mSkipLeadingWhiteSpace = PR_TRUE; mState.mColumn = 0; mState.mKidFrame = nsnull; mState.mPrevKidFrame = nsnull; mState.mKidIndex = aLine->mFirstContentOffset; mState.mKidFrameNum = 0; mState.mMaxElementSize.width = 0; mState.mMaxElementSize.height = 0; mState.mMaxAscent = nsnull; mState.mMaxDescent = nsnull; mSavedState.mKidFrame = nsnull; mBreakFrame = nsnull; mPendingBreak = NS_STYLE_CLEAR_NONE; mLine = aLine; mKidPrevInFlow = nsnull; mNewFrames = 0; mFramesReflowed = 0; mMarginApplied = PR_FALSE; mMustReflowMappedChildren = PR_FALSE; mY = aState.mY; mMaxHeight = aState.mAvailSize.height; mReflowDataChanged = PR_FALSE; mLineHeight = 0; mNoWrap = PR_FALSE; nsIStyleContext* sc; mBlock->GetStyleContext(mPresContext, sc); const nsStyleText* styleText = (const nsStyleText*) sc->GetStyleData(eStyleStruct_Text); switch (styleText->mWhiteSpace) { case NS_STYLE_WHITESPACE_PRE: case NS_STYLE_WHITESPACE_NOWRAP: mNoWrap = PR_TRUE; break; } return rv; } void nsLineLayout::SetReflowSpace(nsRect& aAvailableSpaceRect) { nscoord x0 = aAvailableSpaceRect.x; mLeftEdge = x0; mState.mX = x0; mRightEdge = x0 + aAvailableSpaceRect.width; mMaxWidth = mRightEdge - x0; mReflowDataChanged = PR_TRUE; mMustReflowMappedChildren = PR_TRUE; } nsresult nsLineLayout::SetAscent(nscoord aAscent) { PRInt32 kidFrameNum = mState.mKidFrameNum; if (kidFrameNum == mMaxAscents) { mMaxAscents *= 2; nscoord* newAscents = new nscoord[mMaxAscents]; if (nsnull != newAscents) { nsCRT::memcpy(newAscents, mAscents, sizeof(nscoord) * kidFrameNum); if (mAscents != mAscentBuf) { delete [] mAscents; } mAscents = newAscents; } else { return NS_ERROR_OUT_OF_MEMORY; } } mAscents[kidFrameNum] = aAscent; return NS_OK; } void nsLineLayout::AtSpace() { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::AtSpace: kidFrame=%p kidIndex=%d mX=%d", mState.mKidFrame, mState.mKidIndex, mState.mX)); mBreakFrame = nsnull; mSavedState.mKidFrame = nsnull; } void nsLineLayout::AtWordStart(nsIFrame* aFrame, nscoord aX) { if (nsnull == mBreakFrame) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::AtWordStart: aFrame=%p kidFrame=%p[%d] aX=%d mX=%d", aFrame, mState.mKidFrame, mState.mKidIndex, aX, mState.mX)); mSavedState = mState; mBreakFrame = aFrame; mBreakX = aX; } else if (mBreakFrame == aFrame) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::AtWordStart: update aX=%d", aX)); NS_ASSERTION((mSavedState.mKidFrame == mState.mKidFrame) && (mSavedState.mKidIndex == mState.mKidIndex) && (mSavedState.mKidFrameNum == mState.mKidFrameNum), "bad break state"); mBreakX = aX; } } PRBool nsLineLayout::CanBreak() { if (nsnull == mBreakFrame) { // There is no word to break at NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::CanBreak: no break frame")); return PR_FALSE; } if (mSavedState.mKidFrame == mLine->mFirstChild) { // The line's first frame contains the break position; we are not // allowed to break if the break would empty the line. if (0 == mBreakX) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::CanBreak: breakX=0")); return PR_FALSE; } } // Compute the break x coordinate in our coordinate system by adding // in the X coordinates of each of the frames between the break // frame and the containing block frame. nscoord breakX = mBreakX; nsIFrame* frame = mBreakFrame; for (;;) { nsRect r; frame->GetRect(r); breakX += r.x; nsIFrame* parent; frame->GetGeometricParent(parent); if (parent == mBlock) { break; } frame = parent; } NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::CanBreak: backup from=%p[%d]/%d to=%p[%d]/%d breakX=%d", mState.mKidFrame, mState.mKidIndex, mState.mKidFrameNum, mSavedState.mKidFrame, mSavedState.mKidIndex, mSavedState.mKidFrameNum, breakX)); // Revert the line layout back to where it was when the break point // was found. mState = mSavedState; // Change the right edge to the breakX so that when we reflow the // child it will stop just before the break point. mRightEdge = breakX; // Forgot word break mSavedState.mKidFrame = nsnull; mBreakFrame = nsnull; return PR_TRUE; } /** * Attempt to avoid reflowing a child by seeing if it's been touched * since the last time it was reflowed. */ nsresult nsLineLayout::ReflowMappedChild() { nsIFrame* kidFrame = mState.mKidFrame; if (mMustReflowMappedChildren || PR_TRUE) { /* NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: must reflow frame=%p[%d]", kidFrame, mKidIndex)); */ return ReflowChild(nsnull, PR_FALSE); } NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: attempt frame=%p[%d]", kidFrame, mState.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; kidFrame->FirstChild(f); if (nsnull != f) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: has children")); return ReflowChild(nsnull, PR_FALSE); } } // If we need the max-element size and we are splittable then we // have to reflow to get it. nsSplittableType splits; kidFrame->IsSplittable(splits); #if 0 if (nsnull != mMaxElementSizePointer) { if (NS_FRAME_IS_SPLITTABLE(splits)) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: need max-element-size")); return ReflowChild(nsnull, PR_FALSE); } } #else // XXX For now, if the child is splittable we reflow it. The reason // is that the text whitespace compression needs to be consulted // here to properly handle reflow avoidance. To do that properly we // really need a first-rate protocol here (WillPlace? // CanAvoidReflow?) that gets the frame involved. if (NS_FRAME_IS_SPLITTABLE(splits)) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: splittable hack")); return ReflowChild(nsnull, PR_FALSE); } #endif nsFrameState state; kidFrame->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(nsnull, PR_FALSE); } if (NS_FRAME_IS_SPLITTABLE(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; kidFrame->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(nsnull, PR_FALSE); } } // 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; kidFrame->GetNextInFlow(nextInFlow); if (nsnull != nextInFlow) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: frame has next-in-flow")); return ReflowChild(nsnull, PR_FALSE); } } // 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 = kidFrame->GetStyleContext(mPresContext, kidSC.AssignRef()); if (NS_OK != rv) { return rv; } const nsStyleDisplay* kidDisplay = (const nsStyleDisplay*) kidSC->GetStyleData(eStyleStruct_Display); if (NS_STYLE_FLOAT_NONE != kidDisplay->mFloats) { // XXX If it floats it needs to go through the normal path so that // PlaceFloater is invoked. return ReflowChild(nsnull, PR_FALSE); } const nsStyleSpacing* kidSpacing = (const nsStyleSpacing*) kidSC->GetStyleData(eStyleStruct_Spacing); PRBool isBlock = PR_FALSE; switch (kidDisplay->mDisplay) { case NS_STYLE_DISPLAY_BLOCK: case NS_STYLE_DISPLAY_LIST_ITEM: if (kidFrame != 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; kidFrame->GetRect(kidRect); nsMargin kidMargin; kidSpacing->CalcMarginFor(kidFrame, 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. nscoord x = mState.mX; if (x + kidMargin.left + kidRect.width > mRightEdge) { 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(nsnull, PR_FALSE); } // Make sure the child will fit. The child always fits if it's the // first child on the line. nscoord availWidth = mRightEdge - x; if (mUnconstrainedWidth || (kidFrame == mLine->mFirstChild) || (totalWidth <= availWidth)) { // 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(nsnull); kidFrame->GetReflowMetrics(mPresContext, kidMetrics); nsSize maxElementSize; nsSize* kidMaxElementSize = nsnull; if (nsnull != mMaxElementSizePointer) { kidMaxElementSize = &maxElementSize; maxElementSize.width = kidRect.width; maxElementSize.height = kidRect.height; } kidRect.x = x + kidMargin.left; kidRect.y = mY; if (NS_LINE_LAYOUT_REFLOW_RESULT_NOT_AWARE == mReflowResult) { mState.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 (NS_FRAME_IS_SPLITTABLE(splits)) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowMappedChild: can't directly fit")); return ReflowChild(nsnull, PR_FALSE); } return NS_LINE_LAYOUT_BREAK_BEFORE; } // Return values: <0 for error // 0 == NS_LINE_LAYOUT nsresult nsLineLayout::ReflowChild(nsReflowCommand* aReflowCommand, PRBool aNewChild) { nsIFrame* kidFrame = mState.mKidFrame; NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowChild: attempt frame=%p[%d] mX=%d availWidth=%d", kidFrame, mState.mKidIndex, mState.mX, mRightEdge - mState.mX)); // Get kid frame's style context nsIStyleContextPtr kidSC; nsresult rv = kidFrame->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 const nsStyleDisplay * kidDisplay = (const nsStyleDisplay*) kidSC->GetStyleData(eStyleStruct_Display); PRBool isBlock = PR_FALSE; PRBool isFirstChild = PRBool(kidFrame == mLine->mFirstChild); switch (kidDisplay->mDisplay) { case NS_STYLE_DISPLAY_NONE: // Make sure the frame remains zero sized. kidFrame->WillReflow(*mPresContext); kidFrame->SetRect(nsRect(mState.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 kidFrame->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 PRBool didBreak = PR_FALSE; reflow_it_again_sam: nscoord availWidth = mRightEdge - mState.mX; nsSize kidAvailSize; kidAvailSize.width = availWidth; kidAvailSize.height = mMaxHeight; const nsStyleSpacing* kidSpacing = (const nsStyleSpacing*) kidSC->GetStyleData(eStyleStruct_Spacing); nsMargin kidMargin; kidSpacing->CalcMarginFor(kidFrame, kidMargin); if (!mUnconstrainedWidth) { if (mNoWrap) { // When our reflow is constrained and we are we are not supposed // to wrap make sure the child will fit regardless of how much // space is left. if (isBlock) { // Never give a block an infinite width kidAvailSize.width = mRightEdge - mLeftEdge; } else { kidAvailSize.width = NS_UNCONSTRAINEDSIZE; } } else { kidAvailSize.width -= kidMargin.left + kidMargin.right; if (!isFirstChild && (kidAvailSize.width <= 0)) { // No room. if (!didBreak && CanBreak()) { kidFrame = mState.mKidFrame; didBreak = PR_TRUE; goto reflow_it_again_sam; } // XXX Make sure child is dirty for next time kidFrame->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; nsSize* kidMaxElementSize = nsnull; nsReflowStatus kidReflowStatus; if (nsnull != mMaxElementSizePointer) { kidMaxElementSize = &maxElementSize; } nsReflowMetrics kidMetrics(kidMaxElementSize); // Get reflow reason set correctly. It's possible that we created a // child and then decided that we cannot reflow it (for example, a // block frame that isn't at the start of a line). In this case the // reason will be wrong so we need to check the frame state. nsReflowReason kidReason = eReflowReason_Resize; if (nsnull != aReflowCommand) { kidReason = eReflowReason_Incremental; } else if (aNewChild) { kidReason = eReflowReason_Initial; } else { nsFrameState state; kidFrame->GetFrameState(state); if (NS_FRAME_FIRST_REFLOW & state) { kidReason = eReflowReason_Initial; } } nsReflowState kidReflowState(kidFrame, *mBlockReflowState.reflowState, kidAvailSize, kidReason); kidReflowState.reflowCommand = aReflowCommand; mReflowResult = NS_LINE_LAYOUT_REFLOW_RESULT_NOT_AWARE; nscoord dx = mState.mX + kidMargin.left; NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::ReflowChild: reflowing frame into %d,%d", kidAvailSize.width, kidAvailSize.height)); if (isBlock) { // Calculate top margin by collapsing with previous bottom margin nscoord negTopMargin; nscoord posTopMargin; nsMargin kidMargin; kidSpacing->CalcMarginFor(kidFrame, kidMargin); if (kidMargin.top < 0) { negTopMargin = -kidMargin.top; posTopMargin = 0; } else { negTopMargin = 0; posTopMargin = kidMargin.top; } // XXX if (nav4_compatability) // Calculate the previous margin's positive and negative value. // If the current top margin is not defined by css then just use // what is cached in the block reflow state. Otherwise if the top // margin is defined by css then wipe out any synthetic margin. nscoord prevPos = mBlockReflowState.mPrevPosBottomMargin; nscoord prevNeg = mBlockReflowState.mPrevNegBottomMargin; if (eStyleUnit_Null != kidSpacing->mMargin.GetTopUnit()) { if (mBlockReflowState.mPrevMarginSynthetic) { prevPos = 0; prevNeg = 0; } } else { // When our top margin is not specified by css...act like // ebina's engine. if ((nsnull != mLine->mPrevLine) && !mLine->mPrevLine->mIsBlock) { // Supply a default top margin nsIStyleContext* blockSC; mBlock->GetStyleContext(mPresContext, blockSC); const nsStyleFont* styleFont = (const nsStyleFont*) blockSC->GetStyleData(eStyleStruct_Font); nsIFontMetrics* fm = mPresContext->GetMetricsFor(styleFont->mFont); mBlockReflowState.mPrevNegBottomMargin = 0; mBlockReflowState.mPrevPosBottomMargin = fm->GetHeight(); mBlockReflowState.mPrevMarginSynthetic = PR_TRUE; NS_RELEASE(fm); NS_RELEASE(blockSC); } } nscoord maxPos = PR_MAX(prevPos, posTopMargin); nscoord maxNeg = PR_MAX(prevNeg, negTopMargin); nscoord topMargin = maxPos - maxNeg; // This is no longer a break point mSavedState.mKidFrame = nsnull; mBreakFrame = nsnull; 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); kidFrame->WillReflow(*mPresContext); kidFrame->MoveTo(dx, mY); rv = mBlock->ReflowBlockChild(kidFrame, mPresContext, mSpaceManager, kidMetrics, kidReflowState, kidRect, kidReflowStatus); mSpaceManager->Translate(-dx, -mY); if ((0 == kidRect.width) && (0 == kidRect.height)) { // When a block child collapses into nothingness we don't apply // it's margins. mY -= topMargin; mBlockReflowState.mY -= topMargin; } else { // XXX if (nav4_compatability) // If the block frame we just reflowed has no bottom margin as // specified by css, then we supply our own. if (eStyleUnit_Null == kidSpacing->mMargin.GetBottomUnit()) { // ebina's engine uses the height of the font for the bottom margin. nsIStyleContext* blockSC; mBlock->GetStyleContext(mPresContext, blockSC); const nsStyleFont* styleFont = (const nsStyleFont*) blockSC->GetStyleData(eStyleStruct_Font); nsIFontMetrics* fm = mPresContext->GetMetricsFor(styleFont->mFont); mBlockReflowState.mPrevNegBottomMargin = 0; mBlockReflowState.mPrevPosBottomMargin = fm->GetHeight(); mBlockReflowState.mPrevMarginSynthetic = PR_TRUE; NS_RELEASE(fm); NS_RELEASE(blockSC); } else { // 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; } mBlockReflowState.mPrevMarginSynthetic = PR_FALSE; } } kidRect.x = dx; kidRect.y = mY; kidMetrics.width = kidRect.width; kidMetrics.height = kidRect.height; kidMetrics.ascent = kidRect.height; kidMetrics.descent = 0; } else { // Apply bottom margin speculatively before reflowing the child nscoord bottomMargin = mBlockReflowState.mPrevPosBottomMargin - mBlockReflowState.mPrevNegBottomMargin; if (!mMarginApplied) { // Before we place the first inline child on this line apply // the previous block's bottom margin. mY += bottomMargin; mBlockReflowState.mY += bottomMargin; } kidFrame->WillReflow(*mPresContext); kidFrame->MoveTo(dx, mY); rv = mBlock->ReflowInlineChild(kidFrame, mPresContext, kidMetrics, kidReflowState, kidReflowStatus); // See if speculative application of the margin should stick if (!mMarginApplied) { if (0 == kidMetrics.height) { // No, undo margin application when we get a zero height child. mY -= bottomMargin; mBlockReflowState.mY -= bottomMargin; } else { // Yes, keep the margin application. mMarginApplied = PR_TRUE; mBlockReflowState.mPrevPosBottomMargin = 0; mBlockReflowState.mPrevNegBottomMargin = 0; // XXX tell block what bottomMargin ended up being so that it can // undo it if it ends up pushing the line. } } 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) { if (!isFirstChild) { if (!didBreak && CanBreak()) { kidFrame = mState.mKidFrame; didBreak = PR_TRUE; goto reflow_it_again_sam; } else { // 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; } } } // Non-aware children that take up space act like words; which means // that space immediately following them must not be skipped over. if (NS_LINE_LAYOUT_REFLOW_RESULT_NOT_AWARE == mReflowResult) { if ((kidRect.width != 0) && (kidRect.height != 0)) { mState.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(nsRect& kidRect, const nsReflowMetrics& kidMetrics, const nsSize* kidMaxElementSize, const nsMargin& kidMargin, nsReflowStatus kidReflowStatus) { nscoord horizontalMargins = 0; // Special case to position outside list bullets. // XXX RTL bullets PRBool isBullet = PR_FALSE; if (mBlockReflowState.mListPositionOutside) { PRBool isFirstChild = PRBool(mState.mKidFrame == mLine->mFirstChild); PRBool isFirstLine = PRBool(nsnull == mLine->mPrevLine); if (isFirstChild && isFirstLine) { nsIFrame* blockPrevInFlow; mBlock->GetPrevInFlow(blockPrevInFlow); PRBool isFirstInFlow = PRBool(nsnull == blockPrevInFlow); if (isFirstInFlow) { isBullet = PR_TRUE; // We are placing the first child of the block therefore this // is the bullet that is being reflowed. The bullet is placed // in the padding area of this block. Don't worry about // getting the Y coordinate of the bullet right (vertical // alignment will take care of that). // Compute gap between bullet and inner rect left edge nsIStyleContext* blockCX; mBlock->GetStyleContext(mPresContext, blockCX); const nsStyleFont* font = (const nsStyleFont*)blockCX->GetStyleData(eStyleStruct_Font); NS_RELEASE(blockCX); nsIFontMetrics* fm = mPresContext->GetMetricsFor(font->mFont); nscoord kidAscent = fm->GetMaxAscent(); nscoord dx = fm->GetHeight() / 2; // from old layout engine NS_RELEASE(fm); // XXX RTL bullets kidRect.x = mState.mX - kidRect.width - dx; mState.mKidFrame->SetRect(kidRect); } } } if (!isBullet) { // Place normal in-flow child mState.mKidFrame->SetRect(kidRect); // Advance // XXX RTL horizontalMargins = kidMargin.left + kidMargin.right; nscoord totalWidth = kidMetrics.width + horizontalMargins; mState.mX += totalWidth; } NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::PlaceChild: frame=%p[%d] {%d, %d, %d, %d}", mState.mKidFrame, mState.mKidIndex, kidRect.x, kidRect.y, kidRect.width, kidRect.height)); if (nsnull != mMaxElementSizePointer) { // XXX I'm not certain that this is doing the right thing; rethink this nscoord elementWidth = kidMaxElementSize->width + horizontalMargins; if (elementWidth > mState.mMaxElementSize.width) { mState.mMaxElementSize.width = elementWidth; } if (kidMetrics.height > mState.mMaxElementSize.height) { mState.mMaxElementSize.height = kidMetrics.height; } } if (kidMetrics.ascent > mState.mMaxAscent) { mState.mMaxAscent = kidMetrics.ascent; } if (kidMetrics.descent > mState.mMaxDescent) { mState.mMaxDescent = kidMetrics.descent; } SetAscent(mLine->mIsBlock ? 0 : kidMetrics.ascent); // Set completion status nsresult rv = NS_LINE_LAYOUT_COMPLETE; mLine->mLastContentOffset = mState.mKidIndex; if (NS_FRAME_IS_COMPLETE(kidReflowStatus)) { mLine->mLastContentIsComplete = PR_TRUE; if (mLine->mIsBlock || (NS_LINE_LAYOUT_REFLOW_RESULT_BREAK_AFTER == mReflowResult) || (NS_STYLE_CLEAR_NONE != mPendingBreak)) { rv = NS_LINE_LAYOUT_BREAK_AFTER; if (NS_STYLE_CLEAR_LINE == mPendingBreak) { mPendingBreak = NS_STYLE_CLEAR_NONE; } } mKidPrevInFlow = nsnull; } else { mLine->mLastContentIsComplete = PR_FALSE; rv = NS_LINE_LAYOUT_NOT_COMPLETE; mKidPrevInFlow = mState.mKidFrame; } NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::PlaceChild: rv=%d", rv)); return rv; } nsresult nsLineLayout::IncrementalReflowFromChild(nsReflowCommand* aReflowCommand, nsIFrame* aChildFrame) { nsresult reflowStatus = NS_LINE_LAYOUT_COMPLETE; mLine->mBounds.x = mState.mX; mLine->mBounds.y = mY; mState.mKidFrame = mLine->mFirstChild; mState.mKidFrameNum = 0; while (mState.mKidFrameNum < mLine->mChildCount) { nsresult childReflowStatus; if (mState.mKidFrame == aChildFrame) { childReflowStatus = ReflowChild(aReflowCommand, PR_FALSE); } else { childReflowStatus = ReflowMappedChild(); } if (childReflowStatus < 0) { reflowStatus = childReflowStatus; goto done; } switch (childReflowStatus) { default: case NS_LINE_LAYOUT_COMPLETE: mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame->GetNextSibling(mState.mKidFrame); mState.mKidIndex++; mState.mKidFrameNum++; break; case NS_LINE_LAYOUT_NOT_COMPLETE: reflowStatus = childReflowStatus; mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame->GetNextSibling(mState.mKidFrame); mState.mKidFrameNum++; goto split_line; case NS_LINE_LAYOUT_BREAK_BEFORE: reflowStatus = childReflowStatus; goto split_line; case NS_LINE_LAYOUT_BREAK_AFTER: reflowStatus = childReflowStatus; mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame->GetNextSibling(mState.mKidFrame); mState.mKidIndex++; mState.mKidFrameNum++; split_line: reflowStatus = SplitLine(childReflowStatus); goto done; } } done: // Perform alignment operations AlignChildren(); // Set final bounds of the line mLine->mBounds.height = mLineHeight; mLine->mBounds.width = mState.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 pushCount = mLine->mChildCount - mState.mKidFrameNum; 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; mState.mPrevKidFrame->GetNextInFlow(nextInFlow); if (nsnull == nextInFlow) { // Create a continuation frame for the child frame and insert it // into our lines child list. nsIFrame* nextFrame; mState.mPrevKidFrame->GetNextSibling(nextFrame); nsIStyleContext* kidSC; mState.mPrevKidFrame->GetStyleContext(mPresContext, kidSC); mState.mPrevKidFrame->CreateContinuingFrame(mPresContext, mBlock, kidSC, nextInFlow); NS_RELEASE(kidSC); if (nsnull == nextInFlow) { return NS_ERROR_OUT_OF_MEMORY; } mState.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. mState.mKidFrame = nextInFlow; pushCount++; NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::SplitLine: created next in flow %p", nextInFlow)); } } NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsLineLayout::SplitLine: pushing %d frames", pushCount)); if (0 != pushCount) { NS_ASSERTION(nsnull != mState.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; mState.mKidFrame->GetContentIndex(kidIndexInParent); to->mFirstChild = mState.mKidFrame; to->mChildCount += pushCount; 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 -= pushCount; 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", pushCount); from->List(stdout, 1); printf("After push, to-line:\n"); to->List(stdout, 1); #endif } NS_FRAME_LOG(NS_FRAME_TRACE_CALLS, ("nsLineLayout::SplitLine: returning %d", aChildReflowStatus)); return aChildReflowStatus; } //---------------------------------------------------------------------- nsresult nsLineLayout::ReflowMapped() { nsresult reflowStatus = NS_LINE_LAYOUT_COMPLETE; mState.mKidFrame = mLine->mFirstChild; mState.mKidFrameNum = 0; while (mState.mKidFrameNum < mLine->mChildCount) { nsresult childReflowStatus = ReflowMappedChild(); if (childReflowStatus < 0) { reflowStatus = childReflowStatus; goto done; } switch (childReflowStatus) { default: case NS_LINE_LAYOUT_COMPLETE: mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame->GetNextSibling(mState.mKidFrame); mState.mKidIndex++; mState.mKidFrameNum++; break; case NS_LINE_LAYOUT_NOT_COMPLETE: reflowStatus = childReflowStatus; mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame->GetNextSibling(mState.mKidFrame); mState.mKidFrameNum++; goto split_line; case NS_LINE_LAYOUT_BREAK_BEFORE: reflowStatus = childReflowStatus; goto split_line; case NS_LINE_LAYOUT_BREAK_AFTER: reflowStatus = childReflowStatus; mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame->GetNextSibling(mState.mKidFrame); mState.mKidIndex++; mState.mKidFrameNum++; split_line: reflowStatus = SplitLine(childReflowStatus); 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(const 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 = mState.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 mState.mKidFrame = line->mFirstChild; if (nsnull == mState.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) // XXX change this to use the next-line's mIsBlock when possible // (make sure push code set it's properly for this to work: only // from reflow-unmapped) // 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 = mState.mKidFrame->GetStyleContext(mPresContext, kidSC.AssignRef()); if (NS_OK != rv) { return rv; } const nsStyleDisplay* kidDisplay = (const nsStyleDisplay*) kidSC->GetStyleData(eStyleStruct_Display); 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", mState.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. mState.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(); if (childReflowStatus < 0) { reflowStatus = childReflowStatus; goto done; } switch (childReflowStatus) { default: case NS_LINE_LAYOUT_COMPLETE: mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame = nsnull; mState.mKidIndex++; mState.mKidFrameNum++; break; case NS_LINE_LAYOUT_NOT_COMPLETE: reflowStatus = childReflowStatus; mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame = nsnull; mState.mKidFrameNum++; goto split_line; case NS_LINE_LAYOUT_BREAK_BEFORE: reflowStatus = childReflowStatus; goto split_line; case NS_LINE_LAYOUT_BREAK_AFTER: reflowStatus = childReflowStatus; mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame = nsnull; mState.mKidIndex++; mState.mKidFrameNum++; split_line: reflowStatus = SplitLine(childReflowStatus); 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; } const nsStylePosition* kidPosition = (const nsStylePosition*) kidSC->GetStyleData(eStyleStruct_Position); const nsStyleDisplay* kidDisplay = (const nsStyleDisplay*) kidSC->GetStyleData(eStyleStruct_Display); // 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 = nsAbsoluteFrame::NewFrame(&kidFrame, aKid, mBlock); if (NS_OK == rv) { kidFrame->SetStyleContext(mPresContext, kidSC); } } else if (NS_STYLE_FLOAT_NONE != kidDisplay->mFloats) { rv = nsPlaceholderFrame::NewFrame(&kidFrame, aKid, mBlock); if (NS_OK == rv) { kidFrame->SetStyleContext(mPresContext, kidSC); } } else if ((NS_STYLE_OVERFLOW_SCROLL == kidDisplay->mOverflow) || (NS_STYLE_OVERFLOW_AUTO == kidDisplay->mOverflow)) { rv = NS_NewScrollFrame(&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; } if (NS_OK == rv) { // Wrap the frame in a view if necessary rv = nsHTMLFrame::CreateViewForFrame(mPresContext, kidFrame, kidSC, PR_FALSE); if (NS_OK != rv) { return rv; } } mState.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(mState.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 != mState.mPrevKidFrame) { mState.mPrevKidFrame->SetNextSibling(mState.mKidFrame); } if (0 == mLine->mChildCount) { mLine->mFirstChild = mState.mKidFrame; mBlock->SetFirstChild(mState.mKidFrame); } mLine->mChildCount++; nsresult childReflowStatus; 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; // XXX Mark new frame dirty so it gets reflow later on mState.mKidFrame->WillReflow(*mPresContext); goto split_line; } // Reflow new child frame childReflowStatus = ReflowChild(nsnull, PR_TRUE); if (childReflowStatus < 0) { reflowStatus = childReflowStatus; goto done; } switch (childReflowStatus) { default: case NS_LINE_LAYOUT_COMPLETE: mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame = nsnull; mState.mKidFrameNum++; mState.mKidIndex++; break; case NS_LINE_LAYOUT_NOT_COMPLETE: reflowStatus = childReflowStatus; mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame = nsnull; mState.mKidFrameNum++; goto split_line; case NS_LINE_LAYOUT_BREAK_BEFORE: reflowStatus = childReflowStatus; goto split_line; case NS_LINE_LAYOUT_BREAK_AFTER: reflowStatus = childReflowStatus; mState.mPrevKidFrame = mState.mKidFrame; mState.mKidFrame = nsnull; mState.mKidFrameNum++; mState.mKidIndex++; split_line: reflowStatus = SplitLine(childReflowStatus); 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 {%d, %d, %d, %d}", mLine->mChildCount, mLine->mBounds.x, mLine->mBounds.y, mLine->mBounds.width, mLine->mBounds.height)); nsresult rv = NS_LINE_LAYOUT_COMPLETE; mLine->mBounds.x = mState.mX; mLine->mBounds.y = mY; mOldChildCount = mLine->mChildCount; // 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 AlignChildren(); // Set final bounds of the line mLine->mBounds.height = mLineHeight; mLine->mBounds.width = mState.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 {%d, %d, %d, %d}", mLine->mChildCount, mLine->mBounds.x, mLine->mBounds.y, mLine->mBounds.width, mLine->mBounds.height)); return rv; } void nsLineLayout::AlignChildren() { NS_PRECONDITION(mLine->mChildCount == mState.mKidFrameNum, "bad line reflow"); // Block lines don't require (or allow!) alignment if (mLine->mIsBlock) { mLineHeight = mState.mMaxAscent + mState.mMaxDescent; } else { // Avoid alignment when we didn't actually reflow any frames and we // also didn't change the number of frames we had (which means the // pullup code didn't pull anything up). When this happens it means // that nothing changed which means that we can avoid the alignment // work. if ((0 == mFramesReflowed) && (mOldChildCount == mLine->mChildCount)) { mLineHeight = mLine->mBounds.height; } } nsIStyleContextPtr blockSC; mBlock->GetStyleContext(mPresContext, blockSC.AssignRef()); const nsStyleFont* blockFont = (const nsStyleFont*) blockSC->GetStyleData(eStyleStruct_Font); const nsStyleText* blockText = (const nsStyleText*) blockSC->GetStyleData(eStyleStruct_Text); const nsStyleDisplay* blockDisplay = (const nsStyleDisplay*) blockSC->GetStyleData(eStyleStruct_Display); // First vertically align the children on the line; this will // compute the actual line height for us. if (!mLine->mIsBlock) { mLineHeight = nsCSSLayout::VerticallyAlignChildren(mPresContext, mBlock, blockFont, mY, mLine->mFirstChild, mLine->mChildCount, mAscents, mState.mMaxAscent); } // Now horizontally place the children nsCSSLayout::HorizontallyPlaceChildren(mPresContext, mBlock, blockText->mTextAlign, blockDisplay->mDirection, mLine->mFirstChild, mLine->mChildCount, mState.mX - mLeftEdge, mMaxWidth); // Last, apply relative positioning nsCSSLayout::RelativePositionChildren(mPresContext, mBlock, mLine->mFirstChild, mLine->mChildCount); }