Cleaned up margin handling some more; added flags to control behavior of the block frame; fixed table reflow bug

git-svn-id: svn://10.0.0.236/trunk@11984 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
kipp%netscape.com
1998-10-06 00:38:56 +00:00
parent 33c49d9143
commit a38a2a63c2
6 changed files with 1236 additions and 864 deletions

View File

@@ -155,6 +155,7 @@ struct nsBlockReflowState : public nsFrameReflowState {
PRBool mInlineReflowPrepared;
PRUint8 mTextAlign;
PRUint8 mPrevMarginFlags;
nsSize mStyleSize;
PRIntn mStyleSizeFlags;
@@ -274,8 +275,8 @@ public:
virtual PRBool DeleteChildsNextInFlow(nsIPresContext& aPresContext,
nsIFrame* aNextInFlow);
void SetShrinkWrap(PRBool aShrinkWrap) {
mShrinkWrap = aShrinkWrap;
void SetFlags(PRUint32 aFlags) {
mFlags = aFlags;
}
void RecoverLineMargins(nsBlockReflowState& aState,
@@ -283,11 +284,11 @@ public:
nscoord& aTopMarginResult,
nscoord& aBottomMarginResult);
PRBool CalculateMargins(nsBlockReflowState& aState,
LineData* aLine,
PRBool aInlineContext,
nscoord& aTopMarginResult,
nscoord& aBottomMarginResult);
PRUintn CalculateMargins(nsBlockReflowState& aState,
LineData* aLine,
PRBool aInlineContext,
nscoord& aTopMarginResult,
nscoord& aBottomMarginResult);
void SlideFrames(LineData* aLine, nscoord aDY);
@@ -372,7 +373,7 @@ public:
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsIFrame* aFrame,
PRBool aClip);
PRBool aAlwaysRender);
nsresult AppendNewFrames(nsIPresContext& aPresContext, nsIFrame*);
@@ -392,13 +393,9 @@ public:
// For list-item frames, this is the bullet frame.
BulletFrame* mBullet;
// If true then this frame doesn't act like css says, instead it
// shrink wraps around its contents instead of filling out to its
// parents size.
// XXX This is a *good* place to use a subclass and not
// pay for the per-frame storage
PRBool mShrinkWrap;
// Body configuration flags passed into this block when this block
// is used by the body.
PRUint32 mFlags;
};
//----------------------------------------------------------------------
@@ -871,6 +868,8 @@ BulletFrame::FindTextRuns(nsLineLayout& aLineLayout,
#define LINE_IS_BLOCK 0x2
#define LINE_LAST_CONTENT_IS_COMPLETE 0x4
#define LINE_NEED_DID_REFLOW 0x8
#define LINE_TOP_MARGIN_IS_AUTO 0x10
#define LINE_BOTTOM_MARGIN_IS_AUTO 0x20
struct LineData {
LineData(nsIFrame* aFrame, PRInt32 aCount, PRUint16 flags) {
@@ -965,6 +964,23 @@ struct LineData {
PRBool Contains(nsIFrame* aFrame) const;
void SetMarginFlags(PRUintn aFlags) {
mState &= ~(LINE_TOP_MARGIN_IS_AUTO|LINE_BOTTOM_MARGIN_IS_AUTO);
if (NS_CARRIED_TOP_MARGIN_IS_AUTO & aFlags) {
mState |= LINE_TOP_MARGIN_IS_AUTO;
}
if (NS_CARRIED_BOTTOM_MARGIN_IS_AUTO & aFlags) {
mState |= LINE_BOTTOM_MARGIN_IS_AUTO;
}
}
PRUintn GetMarginFlags() {
return ((LINE_TOP_MARGIN_IS_AUTO & mState)
? NS_CARRIED_TOP_MARGIN_IS_AUTO : 0) |
((LINE_BOTTOM_MARGIN_IS_AUTO & mState) ?
NS_CARRIED_BOTTOM_MARGIN_IS_AUTO : 0);
}
#ifdef NS_DEBUG
void Verify();
#endif
@@ -1318,6 +1334,7 @@ nsBlockReflowState::nsBlockReflowState(nsIPresContext& aPresContext,
#endif
mTextAlign = mStyleText->mTextAlign;
mPrevMarginFlags = 0;
nscoord lr = mBorderPadding.left + mBorderPadding.right;
mY = mBorderPadding.top;
@@ -1413,14 +1430,14 @@ nsBlockReflowState::GetAvailableSpace()
nsresult
NS_NewBlockFrame(nsIContent* aContent, nsIFrame* aParentFrame,
nsIFrame*& aNewFrame, PRBool aShrinkWrap)
nsIFrame*& aNewFrame, PRUint32 aFlags)
{
nsBlockFrame* it = new nsBlockFrame(aContent, aParentFrame);
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
aNewFrame = it;
it->SetShrinkWrap(aShrinkWrap);
it->SetFlags(aFlags);
return NS_OK;
}
@@ -1691,6 +1708,13 @@ nsBlockFrame::Reflow(nsIPresContext& aPresContext,
// Replace parent provided reflow state with our own significantly
// more extensive version.
nsBlockReflowState state(aPresContext, aReflowState, aMetrics);
if (NS_BODY_NO_AUTO_MARGINS & mFlags) {
// Pretend that there is a really big bottom margin preceeding the
// first line so that it's margin is never applied.
}
if (NS_BODY_THE_BODY & mFlags) {
state.mIsMarginRoot = PR_TRUE;
}
// Prepare inline-reflow engine
nsInlineReflow inlineReflow(state.mLineLayout, state, this);
@@ -1844,7 +1868,7 @@ nsBlockFrame::ComputeFinalSize(nsBlockReflowState& aState,
// contents or we fluff out to the maximum available width. Note:
// We always shrink wrap when given an unconstrained width.
nscoord contentWidth = aState.mKidXMost + aState.mBorderPadding.right;
if (!mShrinkWrap && !aState.mUnconstrainedWidth) {
if ((0 == (NS_BODY_SHRINK_WRAP & mFlags)) && !aState.mUnconstrainedWidth) {
// Fluff out to the max width if we aren't already that wide
if (contentWidth < aState.maxSize.width) {
contentWidth = aState.maxSize.width;
@@ -1864,7 +1888,14 @@ nsBlockFrame::ComputeFinalSize(nsBlockReflowState& aState,
if (aState.mIsMarginRoot) {
// When we are a margin root make sure that our last childs
// bottom margin is fully applied.
aState.mY += aState.mPrevBottomMargin;
if ((NS_BODY_NO_AUTO_MARGINS & mFlags) &&
(NS_CARRIED_BOTTOM_MARGIN_IS_AUTO & aState.mPrevMarginFlags)) {
// Do not apply last auto margin when the last childs margin
// is auto and we are configured to ignore them.
}
else {
aState.mY += aState.mPrevBottomMargin;
}
}
aState.mY += aState.mBorderPadding.bottom;
aMetrics.height = aState.mY;
@@ -1874,11 +1905,15 @@ nsBlockFrame::ComputeFinalSize(nsBlockReflowState& aState,
if (aState.mIsMarginRoot) {
aMetrics.mCarriedOutTopMargin = 0;
aMetrics.mCarriedOutBottomMargin = 0;
aState.mCollapsedTopMargin = 0;
aMetrics.mCarriedOutMarginFlags = 0;
}
else {
aMetrics.mCarriedOutTopMargin = aState.mCollapsedTopMargin;
aMetrics.mCarriedOutBottomMargin = aState.mPrevBottomMargin;
aMetrics.mCarriedOutMarginFlags = aState.mCarriedOutMarginFlags;
if (NS_CARRIED_BOTTOM_MARGIN_IS_AUTO & aState.mPrevMarginFlags) {
aMetrics.mCarriedOutMarginFlags |= NS_CARRIED_BOTTOM_MARGIN_IS_AUTO;
}
}
// Special check for zero sized content: If our content is zero
@@ -2189,6 +2224,9 @@ nsBlockFrame::FrameAppendedReflow(nsBlockReflowState& aState)
bottomMargin = nsInlineReflow::MaxMargin(aState.mPrevBottomMargin,
bottomMargin);
}
else {
aState.mPrevMarginFlags = firstDirtyLine->GetMarginFlags();
}
aState.mPrevBottomMargin = bottomMargin;
// Advance Y to be below the line.
@@ -2780,70 +2818,120 @@ nsBlockFrame::PrepareInlineReflow(nsBlockReflowState& aState,
aState.mInlineReflowPrepared = PR_TRUE;
}
PRBool
#define HAVE_CARRIED_MARGIN 0x100
// XXX switch to two versions: inline vs. block?
PRUintn
nsBlockFrame::CalculateMargins(nsBlockReflowState& aState,
LineData* aLine,
PRBool aInlineContext,
nscoord& aTopMarginResult,
nscoord& aBottomMarginResult)
{
PRBool haveCarriedMargins = PR_FALSE;
PRUintn result = 0;
nsInlineReflow& ir = *aState.mInlineReflow;
PRBool isFirstNonEmptyLine = (aState.mY == aState.mBorderPadding.top);
nscoord childsCarriedOutTopMargin = ir.GetCarriedOutTopMargin();
nscoord childsCarriedOutBottomMargin = ir.GetCarriedOutBottomMargin();
if (aInlineContext &&
(0 == childsCarriedOutTopMargin) &&
(0 == childsCarriedOutBottomMargin)) {
// In an inline context, when there are no carried out margins we
// do not collapse the childs margins with the carried out
// margins; the childs margins are applied in a different manner
// in inline contexts.
aTopMarginResult = 0;
aBottomMarginResult = 0;
// First get the childs top and bottom margins. For inline
// situations the child frame(s) margins were applied during line
// layout, therefore we consider them zero here.
nscoord childsTopMargin, childsBottomMargin;
PRUintn marginFlags;
if (aInlineContext) {
childsTopMargin = 0;
childsBottomMargin = 0;
marginFlags = ir.GetCarriedOutMarginFlags();
}
else {
haveCarriedMargins = PR_TRUE;
childsTopMargin = ir.GetTopMargin();
childsBottomMargin = ir.GetBottomMargin();
marginFlags = ir.GetMarginFlags();
}
// Compute the collapsed top margin value from the childs margin and
// its carried out top margin.
nscoord childsTopMargin = aInlineContext ? 0 : ir.GetTopMargin();
nscoord collapsedTopMargin =
nsInlineReflow::MaxMargin(childsCarriedOutBottomMargin, childsTopMargin);
if (isFirstNonEmptyLine) {
// If this block is a root for margins then we will apply the
// collapsed top margin value ourselves. Otherwise, we pass it out
// to our parent to apply.
// Get the carried margin values. Note that even in an inline
// situation there may be a carried margin (e.g. an inline frame
// contains a block frame and the block frame has top/bottom
// margins). While CSS doesn't specify what this means, we do to
// improve compatability with poorly formed HTML (and commonly used
// HTML).
nscoord childsCarriedOutTopMargin = ir.GetCarriedOutTopMargin();
nscoord childsCarriedOutBottomMargin = ir.GetCarriedOutBottomMargin();
if (childsCarriedOutTopMargin || childsCarriedOutBottomMargin) {
result |= HAVE_CARRIED_MARGIN;
}
// Compute the collapsed top margin value (this is a generational
// margin collapse not a sibling margin collapse).
nscoord collapsedTopMargin =
nsInlineReflow::MaxMargin(childsCarriedOutBottomMargin, childsTopMargin);
// Now perform sibling to sibling margin collapsing.
collapsedTopMargin = nsInlineReflow::MaxMargin(aState.mPrevBottomMargin,
collapsedTopMargin);
PRBool topMarginIsAuto = PR_FALSE;
if (NS_CARRIED_TOP_MARGIN_IS_AUTO & marginFlags) {
if (aInlineContext) {
topMarginIsAuto = collapsedTopMargin == childsCarriedOutTopMargin;
}
else {
topMarginIsAuto = collapsedTopMargin == childsTopMargin;
}
}
// Compute the collapsed bottom margin value (this is a generational
// margin collapse not a sibling margin collapse).
nscoord collapsedBottomMargin =
nsInlineReflow::MaxMargin(childsCarriedOutBottomMargin,
childsBottomMargin);
if (NS_CARRIED_BOTTOM_MARGIN_IS_AUTO & marginFlags) {
if (aInlineContext) {
if (collapsedBottomMargin == childsCarriedOutBottomMargin) {
result |= NS_CARRIED_BOTTOM_MARGIN_IS_AUTO;
}
}
else if (collapsedBottomMargin == childsBottomMargin) {
result |= NS_CARRIED_BOTTOM_MARGIN_IS_AUTO;
}
}
// Now that we have the collapsed margin values, address any special
// situations that limit or change how the margins are applied.
if (0 == aLine->mBounds.height) {
// When a line is empty, we collapse its top and bottom margins to
// a single bottom margin value.
// XXX This is not obviously a part of the css2 box model.
collapsedBottomMargin =
nsInlineReflow::MaxMargin(collapsedTopMargin, collapsedBottomMargin);
collapsedTopMargin = 0;
}
else {
PRBool isTopLine = (aState.mY == aState.mBorderPadding.top);
if (isTopLine) {
if (!aState.mIsMarginRoot) {
// We are not a root for margin collapsing and this is our first
// non-empty-line (with a block child presumably). Keep the
// collapsed margin value around to pass out to our parent. We
// don't apply the margin (our parent will) so zero it out.
// non-empty-line (with a block child).
if (topMarginIsAuto) {
// Propogate auto-margin flag outward
aState.mCarriedOutMarginFlags |= NS_CARRIED_TOP_MARGIN_IS_AUTO;
result |= NS_CARRIED_TOP_MARGIN_IS_AUTO;
}
// Keep the collapsed margin value around to pass out to our
// parent. We don't apply its top margin (our parent will) so
// zero it out.
aState.mCollapsedTopMargin = collapsedTopMargin;
collapsedTopMargin = 0;
}
else if (topMarginIsAuto && (NS_BODY_NO_AUTO_MARGINS & mFlags)) {
// Our top margin is an auto margin and we are supposed to
// ignore them. Change it to zero.
collapsedTopMargin = 0;
}
}
else {
// For secondary lines we also collpase the sibling margins. The
// previous lines bottom margin is collapsed with the current
// lines collapsed top margin.
collapsedTopMargin = nsInlineReflow::MaxMargin(aState.mPrevBottomMargin,
collapsedTopMargin);
}
aTopMarginResult = collapsedTopMargin;
// Compute the collapsed bottom margin value. This collapsed value
// will end up in aState.mPrevBottomMargin if the child frame ends
// up being placed in this block frame.
nscoord childsBottomMargin = aInlineContext ? 0 : ir.GetBottomMargin();
nscoord collapsedBottomMargin =
nsInlineReflow::MaxMargin(childsCarriedOutBottomMargin,
childsBottomMargin);
aBottomMarginResult = collapsedBottomMargin;
}
//ListTag(stdout); printf(": line=%p topMargin=%d bottomMargin=%d [%s,%s]\n", aLine, aTopMarginResult, aBottomMarginResult, isFirstNonEmptyLine ? "first" : "!first", aState.mIsMarginRoot ? "root" : "!root");
return haveCarriedMargins;
aTopMarginResult = collapsedTopMargin;
aBottomMarginResult = collapsedBottomMargin;
return result;
}
void
@@ -2876,7 +2964,18 @@ nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState,
nsInlineReflow& ir = *aState.mInlineReflow;
// Prepare the inline reflow engine
PrepareInlineReflow(aState, aFrame, PR_TRUE);
PRBool asBlock = PR_TRUE;
const nsStyleDisplay* display;
nsresult rv = aFrame->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&) display);
if ((NS_OK == rv) && (nsnull != display)) {
switch (display->mDisplay) {
case NS_STYLE_DISPLAY_TABLE:
asBlock = PR_FALSE;
break;
}
}
PrepareInlineReflow(aState, aFrame, asBlock);
ir.SetIsFirstChild((aLine == mLines) &&
(aFrame == aLine->mFirstChild));
@@ -2908,29 +3007,10 @@ nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState,
ir.HorizontalAlignFrames(aLine->mBounds);
ir.RelativePositionFrames();
// Calculate margins (XXX: identical copy is in PlaceLine)
nscoord topMargin = 0, bottomMargin = 0;
PRBool haveCarriedMargins =
CalculateMargins(aState, aLine, PR_FALSE, topMargin, bottomMargin);
if (!haveCarriedMargins) {
// When the child being reflowed has no margin to give, we still
// need to make sure that the previous lines bottom margin is
// applied, but only if the line has a non-zero height (lines with
// no height and no carried margin need to not upset the margin
// state)
if (0 != aLine->mBounds.height) {
topMargin = aState.mPrevBottomMargin;
}
}
else {
// A special hack: if we have carried margins but the line has no
// height then collapse the carried margins down into a single
// (bottom margin) value.
if (0 == aLine->mBounds.height) {
bottomMargin = nsInlineReflow::MaxMargin(topMargin, bottomMargin);
topMargin = 0;
}
}
// Calculate margins
nscoord topMargin, bottomMargin;
PRUintn marginFlags = CalculateMargins(aState, aLine, PR_FALSE,
topMargin, bottomMargin);
// Try to place the frame
nscoord newY = aLine->mBounds.YMost() + topMargin;
@@ -2949,16 +3029,17 @@ nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState,
SlideFrames(aLine, topMargin);
}
// Record bottom margin value for sibling to sibling compression
// or for returning as our carried out bottom margin.
// Adjust running margin value when either we have carried margins
// from the line or we have a non-zero height line.
if (haveCarriedMargins || (0 != aLine->mBounds.height)) {
// Record bottom margin value for sibling to sibling compression or
// for returning as our carried out bottom margin. Adjust running
// margin value when either we have carried margins from the line or
// we have a non-zero height line.
if ((HAVE_CARRIED_MARGIN & marginFlags) || (0 != aLine->mBounds.height)) {
aState.mPrevBottomMargin = bottomMargin;
aState.mPrevMarginFlags = marginFlags;
}
aLine->mCarriedOutTopMargin = ir.GetCarriedOutTopMargin();
aLine->mCarriedOutBottomMargin = ir.GetCarriedOutBottomMargin();
aLine->SetMarginFlags(marginFlags);
nscoord xmost = aLine->mBounds.XMost();
if (xmost > aState.mKidXMost) {
@@ -3284,29 +3365,11 @@ nsBlockFrame::PlaceLine(nsBlockReflowState& aState,
}
}
// Apply margin collapsing when the line has some height
nscoord topMargin = 0, bottomMargin = 0;
PRBool haveCarriedMargins =
CalculateMargins(aState, aLine, PR_TRUE, topMargin, bottomMargin);
if (!haveCarriedMargins) {
// When the child being reflowed has no margin to give, we still
// need to make sure that the previous lines bottom margin is
// applied, but only if the line has a non-zero height (lines with
// no height and no carried margin need to not upset the margin
// state)
if (0 != aLine->mBounds.height) {
topMargin = aState.mPrevBottomMargin;
}
}
else {
// A special hack: if we have carried margins but the line has no
// height then collapse the carried margins down into a single
// (bottom margin) value.
if (0 == aLine->mBounds.height) {
bottomMargin = nsInlineReflow::MaxMargin(topMargin, bottomMargin);
topMargin = 0;
}
}
// Calculate the lines top and bottom margin values. The margin will
// come from an embedded block frame, not from inline frames.
nscoord topMargin, bottomMargin;
PRUintn marginFlags = CalculateMargins(aState, aLine, PR_TRUE,
topMargin, bottomMargin);
// See if the line fit. If it doesn't we need to push it. Our first
// line will always fit.
@@ -3336,11 +3399,13 @@ nsBlockFrame::PlaceLine(nsBlockReflowState& aState,
// Adjust running margin value when either we have carried margins
// from the line or we have a non-zero height line.
if (haveCarriedMargins || (0 != aLine->mBounds.height)) {
if ((HAVE_CARRIED_MARGIN & marginFlags) || (0 != aLine->mBounds.height)) {
aState.mPrevBottomMargin = bottomMargin;
aState.mPrevMarginFlags = marginFlags;
}
aLine->mCarriedOutTopMargin = ir.GetCarriedOutTopMargin();
aLine->mCarriedOutBottomMargin = ir.GetCarriedOutBottomMargin();
aLine->SetMarginFlags(marginFlags);
// Now that we know the line is staying put, put in the outside
// bullet if we have one.
@@ -4322,7 +4387,8 @@ nsBlockFrame::PaintChildren(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect)
{
// Set clip rect so that children don't leak out of us
// Set clip rect so that children don't leak out of us, but only if
// we are supposed to.
const nsStyleDisplay* disp = (const nsStyleDisplay*)
mStyleContext->GetStyleData(eStyleStruct_Display);
PRBool hidden = PR_FALSE;
@@ -4333,34 +4399,37 @@ nsBlockFrame::PaintChildren(nsIPresContext& aPresContext,
hidden = PR_TRUE;
}
// See if we should render everything, or just what can be seen
PRBool renderEverything = PR_TRUE;
if (NS_STYLE_OVERFLOW_VISIBLE != disp->mOverflow) {
renderEverything = PR_FALSE;
}
if ((nsnull == mPrevInFlow) && (nsnull != mBullet)) {
const nsStyleList* list;
GetStyleData(eStyleStruct_List, (const nsStyleStruct*&)list);
if (NS_STYLE_LIST_STYLE_POSITION_OUTSIDE == list->mListStylePosition) {
// Paint the bullet too
PaintChild(aPresContext, aRenderingContext, aDirtyRect, mBullet,
PR_FALSE);
PR_TRUE);
}
}
//XXX ListTag(stdout); printf(": overflow=%d dirtyRect={%d,%d,%d,%d}\n", disp->mOverflow, aDirtyRect);
// Iterate the lines looking for lines that intersect the dirty rect
for (LineData* line = mLines; nsnull != line; line = line->mNext) {
#if XXX
// Stop when we get to a line that's below the dirty rect
if (line->mBounds.y >= aDirtyRect.YMost()) {
break;
}
#endif
// If the line overlaps the dirty rect then iterate the child frames
// and paint those frames that intersect the dirty rect
#if XXX
if (line->mBounds.YMost() > aDirtyRect.y)
#endif
{
if (line->mBounds.YMost() > aDirtyRect.y) {
nsIFrame* kid = line->mFirstChild;
for (PRUint16 i = 0; i < line->mChildCount; i++) {
PaintChild(aPresContext, aRenderingContext, aDirtyRect, kid, PR_TRUE);
PaintChild(aPresContext, aRenderingContext, aDirtyRect, kid,
renderEverything);
kid->GetNextSibling(kid);
}
}
@@ -4376,32 +4445,25 @@ nsBlockFrame::PaintChild(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsIFrame* aFrame,
PRBool aClip)
PRBool aAlwaysRender)
{
nsIView *pView;
aFrame->GetView(pView);
if (nsnull == pView) {
nsRect kidRect;
aFrame->GetRect(kidRect);
nsRect damageArea;
#ifdef NS_DEBUG
PRBool overlap = PR_FALSE;
if (nsIFrame::GetShowFrameBorders() &&
((kidRect.width == 0) || (kidRect.height == 0))) {
nscoord xmost = aDirtyRect.XMost();
nscoord ymost = aDirtyRect.YMost();
if ((aDirtyRect.x <= kidRect.x) && (kidRect.x < xmost) &&
(aDirtyRect.y <= kidRect.y) && (kidRect.y < ymost)) {
overlap = PR_TRUE;
}
PRBool overlap;
if (aAlwaysRender) {
overlap = PR_TRUE;
damageArea = aDirtyRect;
}
else {
overlap = damageArea.IntersectRect(aDirtyRect, kidRect);
}
#else
PRBool overlap = damageArea.IntersectRect(aDirtyRect, kidRect);
#endif
if (overlap || !aClip) {
if (overlap || aAlwaysRender) {
// Translate damage area into kid's coordinate system
nsRect kidDamageArea(damageArea.x - kidRect.x,
damageArea.y - kidRect.y,