/* -*- 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 "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are Copyright (C) 1998 * Netscape Communications Corporation. All Rights Reserved. */ #include "nsCOMPtr.h" #include "nsInlineReflow.h" #include "nsLineLayout.h" #include "nsIFontMetrics.h" #include "nsIPresContext.h" #include "nsISpaceManager.h" #include "nsIStyleContext.h" #include "nsHTMLContainerFrame.h" #include "nsHTMLIIDs.h" #include "nsHTMLAtoms.h" #include "nsStyleConsts.h" #include "nsCRT.h" #ifdef NS_DEBUG #undef NOISY_VERTICAL_ALIGN #else #undef NOISY_VERTICAL_ALIGN #endif #define PLACED_LEFT 0x1 #define PLACED_RIGHT 0x2 // XXX handle DIR=right-to-left nsInlineReflow::nsInlineReflow(nsLineLayout& aLineLayout, const nsHTMLReflowState& aOuterReflowState, nsHTMLContainerFrame* aOuterFrame, PRBool aOuterIsBlock, PRBool aComputeMaxElementSize) : mOuterReflowState(aOuterReflowState), mOuterFrame(aOuterFrame), mSpaceManager(aLineLayout.mSpaceManager), mLineLayout(aLineLayout), mPresContext(aLineLayout.mPresContext), mOuterIsBlock(aOuterIsBlock), mComputeMaxElementSize(aComputeMaxElementSize) { NS_ASSERTION(nsnull != mSpaceManager, "caller must have space manager"); mFrameDataBase = mFrameDataBuf; mNumFrameData = sizeof(mFrameDataBuf) / sizeof(mFrameDataBuf[0]); mMinLineHeight = -1; // Stash away some style data that we need const nsStyleText* styleText; mOuterFrame->GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&) styleText); mTextAlign = styleText->mTextAlign; switch (styleText->mWhiteSpace) { case NS_STYLE_WHITESPACE_PRE: case NS_STYLE_WHITESPACE_NOWRAP: mNoWrap = PR_TRUE; break; default: mNoWrap = PR_FALSE; break; } mDirection = aOuterReflowState.mStyleDisplay->mDirection; mNextRCFrame = nsnull; } nsInlineReflow::~nsInlineReflow() { if (mFrameDataBase != mFrameDataBuf) { delete [] mFrameDataBase; } } void nsInlineReflow::Init(nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight) { #ifdef NS_DEBUG if ((aWidth > 200000) && (aWidth != NS_UNCONSTRAINEDSIZE)) { mOuterFrame->ListTag(stdout); printf(": Init: bad caller: width WAS %d(0x%x)\n", aWidth, aWidth); aWidth = NS_UNCONSTRAINEDSIZE; } if ((aHeight > 200000) && (aHeight != NS_UNCONSTRAINEDSIZE)) { mOuterFrame->ListTag(stdout); printf(": Init: bad caller: height WAS %d(0x%x)\n", aHeight, aHeight); aHeight = NS_UNCONSTRAINEDSIZE; } #endif mLeftEdge = aX; mX = aX; if (NS_UNCONSTRAINEDSIZE == aWidth) { mRightEdge = NS_UNCONSTRAINEDSIZE; } else { mRightEdge = aX + aWidth; } mTopEdge = aY; if (NS_UNCONSTRAINEDSIZE == aHeight) { mBottomEdge = NS_UNCONSTRAINEDSIZE; } else { mBottomEdge = aY + aHeight; } mCarriedOutTopMargin = 0; mCarriedOutBottomMargin = 0; mFrameData = nsnull; mFrameNum = 0; mUpdatedBand = PR_FALSE; mPlacedFloaters = 0; } void nsInlineReflow::UpdateBand(nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight, PRBool aPlacedLeftFloater) { NS_PRECONDITION(mX == mLeftEdge, "update-band called after place-frame"); #ifdef NS_DEBUG if ((aWidth > 200000) && (aWidth != NS_UNCONSTRAINEDSIZE)) { mOuterFrame->ListTag(stdout); printf(": UpdateBand: bad caller: width WAS %d(0x%x)\n", aWidth, aWidth); aWidth = NS_UNCONSTRAINEDSIZE; } if ((aHeight > 200000) && (aHeight != NS_UNCONSTRAINEDSIZE)) { mOuterFrame->ListTag(stdout); printf(": UpdateBand: bad caller: height WAS %d(0x%x)\n", aHeight, aHeight); aHeight = NS_UNCONSTRAINEDSIZE; } #endif mLeftEdge = aX; mX = aX; if (NS_UNCONSTRAINEDSIZE == aWidth) { mRightEdge = NS_UNCONSTRAINEDSIZE; } else { mRightEdge = aX + aWidth; } mTopEdge = aY; if (NS_UNCONSTRAINEDSIZE == aHeight) { mBottomEdge = NS_UNCONSTRAINEDSIZE; } else { mBottomEdge = aY + aHeight; } mUpdatedBand = PR_TRUE; mPlacedFloaters |= (aPlacedLeftFloater ? PLACED_LEFT : PLACED_RIGHT); } void nsInlineReflow::UpdateFrames() { if (NS_STYLE_DIRECTION_LTR == mDirection) { if (PLACED_LEFT & mPlacedFloaters) { PerFrameData* pfd = mFrameDataBase; PerFrameData* end = pfd + mFrameNum; for (; pfd < end; pfd++) { pfd->mBounds.x = mX; } } } else if (PLACED_RIGHT & mPlacedFloaters) { // XXX handle DIR=right-to-left } } nsresult nsInlineReflow::SetFrame(nsIFrame* aFrame) { // Make sure we have a PerFrameData for this frame PRInt32 frameNum = mFrameNum; if (frameNum == mNumFrameData) { mNumFrameData *= 2; PerFrameData* newData = new PerFrameData[mNumFrameData]; if (nsnull == newData) { return NS_ERROR_OUT_OF_MEMORY; } nsCRT::memcpy(newData, mFrameDataBase, sizeof(PerFrameData) * frameNum); if (mFrameDataBase != mFrameDataBuf) { delete [] mFrameDataBase; } mFrameDataBase = newData; } mFrameData = mFrameDataBase + mFrameNum; // We can break before the frame if we placed at least one frame on // the line. mCanBreakBeforeFrame = mLineLayout.GetPlacedFrames() > 0; mFrameData->mFrame = aFrame; return NS_OK; } nsresult nsInlineReflow::ReflowFrame(nsIFrame* aFrame, PRBool aIsAdjacentWithTop, nsReflowStatus& aReflowStatus) { // Prepare for reflowing the frame nsresult rv = SetFrame(aFrame); if (NS_FAILED(rv)) { return rv; } // Compute the available size for the frame. nsSize availSize; if (NS_UNCONSTRAINEDSIZE == mRightEdge) { availSize.width = NS_UNCONSTRAINEDSIZE; } else { availSize.width = mRightEdge - mX; if (mNoWrap) { availSize.width = mOuterReflowState.availableWidth; } } if (NS_UNCONSTRAINEDSIZE == mBottomEdge) { availSize.height = NS_UNCONSTRAINEDSIZE; } else { availSize.height = mBottomEdge - mTopEdge; } // Get reflow reason set correctly. It's possible that a child was // created and then it was decided that it could not be reflowed // (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 reason = eReflowReason_Resize; nsFrameState state; aFrame->GetFrameState(&state); if (NS_FRAME_FIRST_REFLOW & state) { reason = eReflowReason_Initial; } else if (mNextRCFrame == aFrame) { reason = eReflowReason_Incremental; // Make sure we only incrementally reflow once mNextRCFrame = nsnull; } // Setup reflow state for reflowing the frame nsHTMLReflowState reflowState(mPresContext, mOuterReflowState, aFrame, availSize, reason); reflowState.lineLayout = &mLineLayout; if (!aIsAdjacentWithTop) { reflowState.isTopOfPage = PR_FALSE; // make sure this is cleared } mLineLayout.SetUnderstandsWhiteSpace(PR_FALSE); // Stash copies of some of the computed state away for later // (vertical alignment, for example) PerFrameData* pfd = mFrameData; pfd->mMargin = reflowState.computedMargin; pfd->mBorderPadding = reflowState.mComputedBorderPadding; pfd->mFrameType = reflowState.frameType; // Capture this state *before* we reflow the frame in case it clears // the state out. We need to know how to treat the current frame // when breaking. mInWord = mLineLayout.InWord(); // Apply left margins (as appropriate) to the frame computing the // new starting x,y coordinates for the frame. ApplyLeftMargin(reflowState); // Let frame know that are reflowing it nscoord x = pfd->mBounds.x; nscoord y = pfd->mBounds.y; nsIHTMLReflow* htmlReflow; aFrame->QueryInterface(kIHTMLReflowIID, (void**)&htmlReflow); htmlReflow->WillReflow(mPresContext); // Adjust spacemanager coordinate system for the frame. The // spacemanager coordinates are inside the mOuterFrame's // border+padding, but the x/y coordinates are not (recall that // frame coordinates are relative to the parents origin and that the // parents border/padding is inside the parent // frame. Therefore we have to subtract out the parents // border+padding before translating. nsSize innerMaxElementSize; nsHTMLReflowMetrics metrics(mComputeMaxElementSize ? &innerMaxElementSize : nsnull); #ifdef DEBUG if (mComputeMaxElementSize) { metrics.maxElementSize->width = nscoord(0xdeadbeef); metrics.maxElementSize->height = nscoord(0xdeadbeef); } #endif nscoord tx = x - mOuterReflowState.mComputedBorderPadding.left; nscoord ty = y - mOuterReflowState.mComputedBorderPadding.top; mSpaceManager->Translate(tx, ty); htmlReflow->Reflow(mPresContext, metrics, reflowState, aReflowStatus); mSpaceManager->Translate(-tx, -ty); #ifdef DEBUG_kipp NS_ASSERTION((metrics.width > -200000) && (metrics.width < 200000), "oy"); NS_ASSERTION((metrics.height > -200000) && (metrics.height < 200000), "oy"); #endif #ifdef DEBUG if (mComputeMaxElementSize && ((nscoord(0xdeadbeef) == metrics.maxElementSize->width) || (nscoord(0xdeadbeef) == metrics.maxElementSize->height))) { printf("nsInlineReflow: "); nsFrame::ListTag(stdout, aFrame); printf(" didn't set max-element-size!\n"); metrics.maxElementSize->width = 0; metrics.maxElementSize->height = 0; } #endif aFrame->GetFrameState(&state); if (NS_FRAME_OUTSIDE_CHILDREN & state) { pfd->mCombinedArea = metrics.mCombinedArea; } else { pfd->mCombinedArea.x = 0; pfd->mCombinedArea.y = 0; pfd->mCombinedArea.width = metrics.width; pfd->mCombinedArea.height = metrics.height; } pfd->mBounds.width = metrics.width; pfd->mBounds.height = metrics.height; if (mComputeMaxElementSize) { pfd->mMaxElementSize = *metrics.maxElementSize; } // Now that frame has been reflowed at least one time make sure that // the NS_FRAME_FIRST_REFLOW bit is cleared so that never give it an // initial reflow reason again. if (eReflowReason_Initial == reason) { aFrame->GetFrameState(&state); aFrame->SetFrameState(state & ~NS_FRAME_FIRST_REFLOW); } if (!NS_INLINE_IS_BREAK_BEFORE(aReflowStatus)) { // If frame is complete and has a next-in-flow, we need to delete // them now. Do not do this when a break-before is signaled because // the frame is going to get reflowed again (and may end up wanting // a next-in-flow where it ends up). if (NS_FRAME_IS_COMPLETE(aReflowStatus)) { nsIFrame* kidNextInFlow; aFrame->GetNextInFlow(&kidNextInFlow); if (nsnull != kidNextInFlow) { // Remove all of the childs next-in-flows. Make sure that we ask // the right parent to do the removal (it's possible that the // parent is not this because we are executing pullup code) nsHTMLContainerFrame* parent; aFrame->GetParent((nsIFrame**) &parent); parent->DeleteChildsNextInFlow(mPresContext, aFrame); } } } // Reflow the frame. If the frame must be placed somewhere else // then we return immediately. if (!NS_INLINE_IS_BREAK_BEFORE(aReflowStatus)) { // See if we can place the frame. If we can't fit it, then we // return now. if (CanPlaceFrame(reflowState, metrics, aReflowStatus)) { // Place the frame, updating aBounds with the final size and // location. Then apply the bottom+right margins (as // appropriate) to the frame. PlaceFrame(metrics); } } return rv; } void nsInlineReflow::ApplyLeftMargin(const nsHTMLReflowState& aReflowState) { PerFrameData* pfd = mFrameData; // If this is the first frame of a block, and its the first line of // a block then see if the text-indent property amounts to anything. nscoord indent = 0; if (mOuterIsBlock && (0 == mLineLayout.GetLineNumber()) && (0 == mFrameNum)) { const nsStyleText* text; mOuterFrame->GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&) text); nsStyleUnit unit = text->mTextIndent.GetUnit(); if (eStyleUnit_Coord == unit) { indent = text->mTextIndent.GetCoordValue(); } else if (eStyleUnit_Percent == unit) { nscoord width = nsHTMLReflowState::GetContainingBlockContentWidth(mOuterReflowState.parentReflowState); if (0 != width) { indent = nscoord(text->mTextIndent.GetPercentValue() * width); } } } pfd->mBounds.x = mX + indent; pfd->mBounds.y = mTopEdge; // Compute left margin nsIFrame* prevInFlow; switch (aReflowState.mStyleDisplay->mFloats) { default: NS_NOTYETIMPLEMENTED("Unsupported floater type"); // FALL THROUGH case NS_STYLE_FLOAT_LEFT: case NS_STYLE_FLOAT_RIGHT: // When something is floated, its margins are applied there // not here. break; case NS_STYLE_FLOAT_NONE: // Only apply left-margin on the first-in flow for inline frames pfd->mFrame->GetPrevInFlow(&prevInFlow); if (nsnull != prevInFlow) { // Zero this out so that when we compute the max-element-size // of the frame we will properly avoid adding in the left // margin. pfd->mMargin.left = 0; } pfd->mBounds.x += pfd->mMargin.left; break; } } /** * See if the frame can be placed now that we know it's desired size. * We can always place the frame if the line is empty. Note that we * know that the reflow-status is not a break-before because if it was * ReflowFrame above would have returned false, preventing this method * from being called. The logic in this method assumes that. * * Note that there is no check against the Y coordinate because we * assume that the caller will take care of that. */ PRBool nsInlineReflow::CanPlaceFrame(const nsHTMLReflowState& aReflowState, nsHTMLReflowMetrics& aMetrics, nsReflowStatus& aStatus) { PerFrameData* pfd = mFrameData; // Compute right margin to use mRightMargin = 0; if (0 != pfd->mBounds.width) { switch (aReflowState.mStyleDisplay->mFloats) { default: NS_NOTYETIMPLEMENTED("Unsupported floater type"); // FALL THROUGH case NS_STYLE_FLOAT_LEFT: case NS_STYLE_FLOAT_RIGHT: // When something is floated, its margins are applied there // not here. break; case NS_STYLE_FLOAT_NONE: // Only apply right margin for the last-in-flow if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { // Zero this out so that when we compute the // max-element-size of the frame we will properly avoid // adding in the right margin. pfd->mMargin.right = 0; } mRightMargin = pfd->mMargin.right; break; } } // Set outside to PR_TRUE if the result of the reflow leads to the // frame sticking outside of our available area. PRBool outside = pfd->mBounds.XMost() + mRightMargin > mRightEdge; // There are several special conditions that exist which allow us to // ignore outside. If they are true then we can place frame and // return PR_TRUE. if (!mCanBreakBeforeFrame || mInWord || mNoWrap) { return PR_TRUE; } if (0 == pfd->mMargin.left + pfd->mBounds.width + pfd->mMargin.right) { // Empty frames always fit right where they are return PR_TRUE; } if (0 == mFrameNum) { return PR_TRUE; } if (outside) { aStatus = NS_INLINE_LINE_BREAK_BEFORE(); return PR_FALSE; } return PR_TRUE; #if XXX // If this is the first frame going into this inline reflow or it's // the first placed frame in the line or wrapping is disabled then // the frame fits regardless of who much room there is. This // guarantees that we always make progress regardless of the // limitations of the reflow area. If the container reflowing this // frame ends up too big then the container may be pushable to a new // location. if ((0 == mFrameNum) || (0 == mLineLayout.GetPlacedFrames()) || mOuterReflowState.mNoWrap || mInWord) { return PR_TRUE; } // See if the frame fits. If it doesn't then we fabricate up a // break-before status. if (pfd->mBounds.XMost() + mRightMargin > mRightEdge) { aStatus = NS_INLINE_LINE_BREAK_BEFORE(); return PR_FALSE; } return PR_TRUE; #endif } /** * Place the frame. Update running counters. */ void nsInlineReflow::PlaceFrame(nsHTMLReflowMetrics& aMetrics) { PerFrameData* pfd = mFrameData; //XXX disabled: css2 spec claims otherwise; the problem is we need to //handle continuations differently so that empty continuations disappear // If frame is zero width then do not apply its left and right margins. PRBool emptyFrame = PR_FALSE; if ((0 == pfd->mBounds.width) && (0 == pfd->mBounds.height)) { pfd->mBounds.x = mX; pfd->mBounds.y = mTopEdge; emptyFrame = PR_TRUE; } // Record ascent and update max-ascent and max-descent values pfd->mAscent = aMetrics.ascent; pfd->mDescent = aMetrics.descent; mFrameNum++; // If the band was updated during the reflow of that frame then we // need to adjust any prior frames that were reflowed. if (mUpdatedBand && mOuterIsBlock) { UpdateFrames(); } mUpdatedBand = PR_FALSE; // Save carried out information for caller mCarriedOutTopMargin = aMetrics.mCarriedOutTopMargin; mCarriedOutBottomMargin = aMetrics.mCarriedOutBottomMargin; // Advance to next X coordinate mX = pfd->mBounds.XMost() + mRightMargin; // If the frame is a not aware of white-space and it takes up some // area, disable leading white-space compression for the next frame // to be reflowed. if (!mLineLayout.GetUnderstandsWhiteSpace() && !emptyFrame) { mLineLayout.SetEndsInWhiteSpace(PR_FALSE); } // Compute the bottom margin to apply. Note that the margin only // applies if the frame ends up with a non-zero height. if (!emptyFrame) { // Inform line layout that we have placed a non-empty frame mLineLayout.AddPlacedFrame(mFrameData->mFrame); } } void nsInlineReflow::AddFrame(nsIFrame* aFrame, const nsHTMLReflowMetrics& aMetrics) { SetFrame(aFrame); PerFrameData* pfd = mFrameDataBase + mFrameNum; mFrameNum++; pfd->mFrameType = NS_CSS_FRAME_TYPE_INLINE|NS_FRAME_REPLACED_ELEMENT; pfd->mAscent = aMetrics.ascent; pfd->mDescent = aMetrics.descent; pfd->mMargin.SizeTo(0, 0, 0, 0); pfd->mBorderPadding.SizeTo(0, 0, 0, 0); aFrame->GetRect(pfd->mBounds); // y value is irrelevant pfd->mCombinedArea = aMetrics.mCombinedArea; if (mComputeMaxElementSize) { pfd->mMaxElementSize.SizeTo(aMetrics.width, aMetrics.height); } } void nsInlineReflow::RemoveFrame(nsIFrame* aFrame) { PerFrameData* pfd = mFrameDataBase; PerFrameData* last = pfd + mFrameNum - 1; while (pfd <= last) { if (pfd->mFrame == aFrame) { mFrameNum--; if (pfd != last) { // Slide down the other structs over the vacancy nsCRT::memmove(pfd, pfd + 1, (last - pfd) * sizeof(PerFrameData)); } break; } pfd++; } } PRBool nsInlineReflow::IsZeroHeight() const { PerFrameData* pfd = mFrameDataBase; PerFrameData* last = pfd + mFrameNum - 1; while (pfd <= last) { if (0 != pfd->mBounds.height) { return PR_FALSE; } pfd++; } return PR_TRUE; } // XXX what about ebina's center vs. ncsa-center? // XXX Make sure that when a block or inline is reflowing an inline // frame that is wrapping an anonymous block that none of the // alignment routines are used. // XXX avoid during pass1 table reflow void nsInlineReflow::VerticalAlignFrames(nsRect& aLineBox, nscoord& aMaxAscent, nscoord& aMaxDescent) { PerFrameData* pfd0 = mFrameDataBase; PerFrameData* end = pfd0 + mFrameNum; // XXX I think that the line box should wrap around the children, // even if they have negative margins, right? aLineBox.x = mLeftEdge; aLineBox.y = mTopEdge; aLineBox.width = mX - mLeftEdge; // Get the parent elements font in case we need it const nsStyleFont* font; mOuterFrame->GetStyleData(eStyleStruct_Font, (const nsStyleStruct*&)font); nsCOMPtr fm; mPresContext.GetMetricsFor(font->mFont, getter_AddRefs(fm)); #ifdef NOISY_VERTICAL_ALIGN mOuterFrame->ListTag(stdout); printf(": valign frames (count=%d, line#%d)\n", mFrameNum, mLineLayout.GetLineNumber()); #endif // Examine each and determine the minYTop, the maxYBottom and the // maximum height. We will use these values to determine the final // height of the line box and then position each frame. nscoord minYTop = 0; nscoord maxYBottom = 0; nscoord maxTopHeight = 0; nscoord maxBottomHeight = 0; PerFrameData* pfd; for (pfd = pfd0; pfd < end; pfd++) { PRUint8 verticalAlignEnum; nscoord fontParam; nsIFrame* frame = pfd->mFrame; // yTop = Y coordinate for the top of frame box relative to // the baseline of the linebox which is assumed to be at Y=0 nscoord yTop; // Compute vertical height of frame; add in margins (note: if the // margins are for an inline-non-replaced frame then other code // has forced them to zero). nscoord height = pfd->mBounds.height; height += pfd->mMargin.top + pfd->mMargin.bottom; pfd->mAscent += pfd->mMargin.top; if (NS_CSS_FRAME_TYPE_INLINE == pfd->mFrameType) { // According to the CSS2 spec (section 10.8 and 10.8.1) border, // padding and margins around inline non-replaced elements do // not enter into inline box height calculations (and therefore // the line box calculation). To accomplish that here we have to // subtract out the border and padding during vertical alignment // from the inline non-replaced frame height. height -= pfd->mBorderPadding.top + pfd->mBorderPadding.bottom; // When a line-height is specified for an inline-non-replaced // element then its value determines the exact height of the box // for the purposes of vertical alignment and line-height // sizing. nscoord lh = nsHTMLReflowState::CalcLineHeight(mPresContext, frame); #ifdef NOISY_VERTICAL_ALIGN printf(" "); nsFrame::ListTag(stdout, pfd->mFrame); printf(": lineHeight=%d height=%d\n", lh, pfd->mBounds.height); #endif if (lh >= 0) { nscoord leading = lh - height; nscoord topLeading = leading / 2; pfd->mAscent += topLeading; pfd->mMargin.top = topLeading; pfd->mMargin.bottom = leading - topLeading; height = lh; } } const nsStyleText* textStyle; frame->GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&)textStyle); nsStyleUnit verticalAlignUnit = textStyle->mVerticalAlign.GetUnit(); switch (verticalAlignUnit) { case eStyleUnit_Enumerated: verticalAlignEnum = textStyle->mVerticalAlign.GetIntValue(); switch (verticalAlignEnum) { default: case NS_STYLE_VERTICAL_ALIGN_BASELINE: yTop = -pfd->mAscent; break; case NS_STYLE_VERTICAL_ALIGN_SUB: // Align the frames baseline on the subscript baseline fm->GetSubscriptOffset(fontParam); yTop = fontParam - pfd->mAscent; break; case NS_STYLE_VERTICAL_ALIGN_SUPER: // Align the frames baseline on the superscript baseline fm->GetSuperscriptOffset(fontParam); yTop = -fontParam - pfd->mAscent; break; case NS_STYLE_VERTICAL_ALIGN_TOP: if (mOuterIsBlock) { if (height > maxTopHeight) { maxTopHeight = height; } continue; } else { // When parent is not the block, baseline align top/bottom // frames initially. The block will do a post-process after // the line-height is determined to place these frames. yTop = -pfd->mAscent; mLineLayout.RecordPass2VAlignFrame(); } break; case NS_STYLE_VERTICAL_ALIGN_BOTTOM: if (mOuterIsBlock) { if (height > maxBottomHeight) { maxBottomHeight = height; } continue; } else { // When parent is not the block, baseline align top/bottom // frames initially. The block will do a post-process after // the line-height is determined to place these frames. yTop = -pfd->mAscent; mLineLayout.RecordPass2VAlignFrame(); } break; case NS_STYLE_VERTICAL_ALIGN_MIDDLE: // Align the midpoint of the frame with 1/2 the parents x-height fm->GetXHeight(fontParam); yTop = -(fontParam + height)/2; break; case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM: fm->GetMaxDescent(fontParam); yTop = fontParam - height; break; case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP: fm->GetMaxAscent(fontParam); yTop = -fontParam; break; } break; case eStyleUnit_Coord: // According to the CSS2 spec (10.8.1), a positive value // "raises" the box by the given distance while a negative value // "lowers" the box by the given distance (with zero being the // baseline). Since Y coordinates increase towards the bottom of // the screen we reverse the sign. yTop = -pfd->mAscent - textStyle->mVerticalAlign.GetCoordValue(); break; case eStyleUnit_Percent: // Similar to a length value (eStyleUnit_Coord) except that the // percentage is a function of the elements line-height value. yTop = -pfd->mAscent - nscoord(textStyle->mVerticalAlign.GetPercentValue() * pfd->mBounds.height); break; default: yTop = -pfd->mAscent; break; } pfd->mBounds.y = yTop; #ifdef DEBUG_kipp NS_ASSERTION((pfd->mBounds.y >= -200000) && (pfd->mBounds.y < 200000), "yikes"); #endif #ifdef NOISY_VERTICAL_ALIGN printf(" "); nsAutoString tmp; pfd->mFrame->GetFrameName(tmp); fputs(tmp, stdout); printf(": yTop=%d minYTop=%d yBottom=%d maxYBottom=%d [bounds=%d]\n", yTop, minYTop, yTop + height, maxYBottom, pfd->mBounds.height); NS_ASSERTION(yTop >= -200000, "bad yTop"); #endif if (yTop < minYTop) { minYTop = yTop; } // yBottom = Y coordinate for the bottom of the frame box, again // relative to the baseline where Y=0 nscoord yBottom = yTop + height; if (yBottom > maxYBottom) { maxYBottom = yBottom; } } // Once we have finished the above abs(minYTop) represents the // maximum ascent of the line box. "CSS2 spec section 10.8: the line // box height is the distance between the uppermost box top // (minYTop) and the lowermost box bottom (maxYBottom)." nscoord lineHeight = maxYBottom - minYTop; nscoord maxAscent = -minYTop; #ifdef NOISY_VERTICAL_ALIGN printf(" lineHeight=%d maxAscent=%d\n", lineHeight, maxAscent); #endif if (lineHeight < maxTopHeight) { // If the line height ends up shorter than the tallest top aligned // box then the line height must grow but the line's ascent need // not be changed. lineHeight = maxTopHeight; #ifdef NOISY_VERTICAL_ALIGN printf(" *lineHeight=maxTopHeight=%d\n", lineHeight); #endif } if (lineHeight < maxBottomHeight) { // If the line height ends up shorter than the tallest bottom // aligned box then the line height must grow and the line's // ascent needs to be adjusted (so that the baseline aligned // objects move downward). nscoord dy = maxBottomHeight - lineHeight; lineHeight = maxBottomHeight; maxAscent += dy; #ifdef NOISY_VERTICAL_ALIGN printf(" *lineHeight=maxBottomHeight=%d dy=%d\n", lineHeight, dy); #endif } nscoord topEdge = mTopEdge; if (0 != lineHeight) { // CSS2 10.8.1 says that line-height, as applied to blocks, will // provide the *minimum* height of the line (line-height as // applied to a non-replaced inline element defines the exact // height of the inline element, not including borders and // padding). Therefore, we only adjust the line-height here if the // outer container is a block (because if the outer container is // an inline then its line-height will be applied by itself). if (mOuterIsBlock) { nscoord minLineHeight = mMinLineHeight; if (minLineHeight > lineHeight) { // Apply half of the changed line-height to the top and bottom // positioning of each frame. topEdge += (minLineHeight - lineHeight) / 2; lineHeight = minLineHeight; } } } aLineBox.height = lineHeight; #ifdef NOISY_VERTICAL_ALIGN printf(" *lineHeight=newLineHeight=%d topEdgeDelta=%d\n", lineHeight, topEdge - mTopEdge); #endif // Pass2 - position each of the frames mMaxElementSize.SizeTo(0, 0); for (pfd = pfd0; pfd < end; pfd++) { nsIFrame* frame = pfd->mFrame; // Factor in this frame to the max-element-size if (mComputeMaxElementSize) { // The max-element width is the sum of the interior max-element // width plus the left and right margins that are applied to the // frame. nscoord mw = pfd->mMaxElementSize.width + pfd->mMargin.left + pfd->mMargin.right; if (mw > mMaxElementSize.width) { mMaxElementSize.width = mw; } // The max-element height is the sum of the interior max-element // height plus the top and bottom margins. nscoord mh = pfd->mMaxElementSize.height + pfd->mMargin.top + pfd->mMargin.bottom; if (mh > mMaxElementSize.height) { mMaxElementSize.height = mh; } } const nsStyleText* textStyle; frame->GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&)textStyle); nsStyleUnit verticalAlignUnit = textStyle->mVerticalAlign.GetUnit(); if (eStyleUnit_Enumerated == verticalAlignUnit) { PRUint8 verticalAlignEnum = textStyle->mVerticalAlign.GetIntValue(); switch (verticalAlignEnum) { case NS_STYLE_VERTICAL_ALIGN_TOP: #ifdef NOISY_VERTICAL_ALIGN printf(" "); nsFrame::ListTag(stdout, pfd->mFrame); printf(": [top] mTopEdge=%d margin.top=%d\n", mTopEdge, pfd->mMargin.top); #endif pfd->mBounds.y = mTopEdge + pfd->mMargin.top; if (NS_CSS_FRAME_TYPE_INLINE == pfd->mFrameType) { pfd->mBounds.y -= pfd->mBorderPadding.top; } break; case NS_STYLE_VERTICAL_ALIGN_BOTTOM: #ifdef NOISY_VERTICAL_ALIGN printf(" "); nsFrame::ListTag(stdout, pfd->mFrame); printf(": [bottom] mTopEdge=%d lineHeight=%d height=%d margin.bottom=%d\n", mTopEdge, lineHeight, pfd->mBounds.height, pfd->mMargin.bottom); #endif pfd->mBounds.y = mTopEdge + lineHeight - pfd->mBounds.height - pfd->mMargin.bottom; if (NS_CSS_FRAME_TYPE_INLINE == pfd->mFrameType) { pfd->mBounds.y += pfd->mBorderPadding.bottom; } break; default: #ifdef NOISY_VERTICAL_ALIGN printf(" "); nsFrame::ListTag(stdout, pfd->mFrame); printf(": y=%d topEdge=%d maxAscent=%d margin.top=%d\n", pfd->mBounds.y, topEdge, maxAscent, pfd->mMargin.top); #endif pfd->mBounds.y = topEdge + maxAscent + pfd->mBounds.y + pfd->mMargin.top; if (NS_CSS_FRAME_TYPE_INLINE == pfd->mFrameType) { pfd->mBounds.y -= pfd->mBorderPadding.top; } break; } } else { pfd->mBounds.y = topEdge + maxAscent + pfd->mBounds.y + pfd->mMargin.top; if (NS_CSS_FRAME_TYPE_INLINE == pfd->mFrameType) { pfd->mBounds.y -= pfd->mBorderPadding.top; } } #ifdef DEBUG_kipp NS_ASSERTION((pfd->mBounds.y >= -200000) && (pfd->mBounds.y < 200000), "yikes"); #endif frame->SetRect(pfd->mBounds); if (mOuterIsBlock && mLineLayout.NeedPass2VAlign()) { // Perform pass2 vertical alignment for top/bottom aligned // frames that are not our direct descendants. nsIHTMLReflow* ihr; nsresult rv = frame->QueryInterface(kIHTMLReflowIID, (void**)&ihr); if (NS_SUCCEEDED(rv)) { nscoord distanceFromTopEdge = pfd->mBounds.y - mTopEdge; ihr->VerticalAlignFrames(mPresContext, mOuterReflowState, lineHeight, distanceFromTopEdge, pfd->mCombinedArea); } } } aMaxAscent = maxAscent; aMaxDescent = lineHeight - maxAscent; #ifdef NOISY_VERTICAL_ALIGN printf(" ==> "); mOuterFrame->ListTag(stdout); printf(": lineBox=%d,%d,%d,%d maxAscent=%d maxDescent=%d\n", aLineBox.x, aLineBox.y, aLineBox.width, aLineBox.height, aMaxAscent, aMaxDescent); #endif } void nsInlineReflow::TrimTrailingWhiteSpace(nsRect& aLineBox) { #if XXX_still_do_this nscoord deltaWidth; PerFrameData* pfd = mFrameDataBase + (mFrameNum - 1); if (pfd->mBounds.width > 0) { nsIFrame* frame = pfd->mFrame; nsIHTMLReflow* ihr; if (NS_OK == frame->QueryInterface(kIHTMLReflowIID, (void**)&ihr)) { ihr->TrimTrailingWhiteSpace(mPresContext, *mOuterReflowState.rendContext, deltaWidth); aLineBox.width -= deltaWidth; pfd->mBounds.width -= deltaWidth; } } #endif } void nsInlineReflow::HorizontalAlignFrames(nsRect& aLineBox, PRBool aAllowJustify) { if (NS_UNCONSTRAINEDSIZE == mRightEdge) { // Don't bother return; } nscoord maxWidth = mRightEdge - mLeftEdge; if (aLineBox.width < maxWidth) { nscoord dx = 0; switch (mTextAlign) { case NS_STYLE_TEXT_ALIGN_DEFAULT: if (NS_STYLE_DIRECTION_LTR == mDirection) { // default alignment for left-to-right is left so do nothing return; } // Fall through to align right case for default alignment // used when the direction is right-to-left. case NS_STYLE_TEXT_ALIGN_RIGHT: dx = maxWidth - aLineBox.width; break; case NS_STYLE_TEXT_ALIGN_LEFT: return; case NS_STYLE_TEXT_ALIGN_JUSTIFY: // If this is not the last line then go ahead and justify the // frames in the line. If it is the last line then if the // direction is right-to-left then we right-align the frames. if (aAllowJustify) { JustifyFrames(maxWidth, aLineBox); return; } else if (NS_STYLE_DIRECTION_RTL == mDirection) { // right align the frames dx = maxWidth - aLineBox.width; } break; case NS_STYLE_TEXT_ALIGN_CENTER: dx = (maxWidth - aLineBox.width) / 2; break; } if (0 != dx) { // Position children PerFrameData* pfd = mFrameDataBase; PerFrameData* end = pfd + mFrameNum; nsPoint origin; for (; pfd < end; pfd++) { nsIFrame* kid = pfd->mFrame;; kid->GetOrigin(origin); kid->MoveTo(origin.x + dx, origin.y); kid->GetNextSibling(&kid); } } } } // XXX avoid during pass1 table reflow void nsInlineReflow::RelativePositionFrames(nsRect& aCombinedArea) { nscoord x0 = mLeftEdge, y0 = mTopEdge; nscoord x1 = x0, y1 = y0; nsPoint origin; PerFrameData* pfd = mFrameDataBase; PerFrameData* end = pfd + mFrameNum; for (; pfd < end; pfd++) { nscoord x = pfd->mCombinedArea.x + pfd->mBounds.x; nscoord y = pfd->mCombinedArea.y + pfd->mBounds.y; nsIFrame* kid = pfd->mFrame; const nsStylePosition* kidPosition; kid->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&)kidPosition); if (NS_STYLE_POSITION_RELATIVE == kidPosition->mPosition) { kid->GetOrigin(origin); nsStyleCoord coord; nscoord dx = 0; switch (kidPosition->mOffset.GetLeftUnit()) { case eStyleUnit_Percent: printf("XXX: not yet implemented: %% relative position\n"); case eStyleUnit_Auto: break; case eStyleUnit_Coord: dx = kidPosition->mOffset.GetLeft(coord).GetCoordValue(); break; default: break; } nscoord dy = 0; switch (kidPosition->mOffset.GetTopUnit()) { case eStyleUnit_Percent: printf("XXX: not yet implemented: %% relative position\n"); case eStyleUnit_Auto: break; case eStyleUnit_Coord: dy = kidPosition->mOffset.GetTop(coord).GetCoordValue(); break; default: break; } kid->MoveTo(origin.x + dx, origin.y + dy); x += dx; y += dy; } // Compute min and max x/y values for the reflowed frame's // combined areas nscoord xmost = x + pfd->mCombinedArea.width; nscoord ymost = y + pfd->mCombinedArea.height; if (x < x0) x0 = x; if (xmost > x1) x1 = xmost; if (y < y0) y0 = y; if (ymost > y1) y1 = ymost; } aCombinedArea.x = x0; aCombinedArea.y = y0; aCombinedArea.width = x1 - x0; aCombinedArea.height = y1 - y0; } void nsInlineReflow::JustifyFrames(nscoord aMaxWidth, nsRect& aLineBox) { // Gather up raw data for justification PRInt32 i, n = mFrameNum; PerFrameData* pfd = mFrameDataBase; PRInt32 fixed = 0; nscoord fixedWidth = 0; for (i = 0; i < n; i++, pfd++) { nsIFrame* frame = pfd->mFrame; nsSplittableType isSplittable = NS_FRAME_NOT_SPLITTABLE; frame->IsSplittable(isSplittable); if ((0 == pfd->mBounds.width) || NS_FRAME_IS_NOT_SPLITTABLE(isSplittable)) { pfd->mSplittable = PR_FALSE; fixed++; fixedWidth += pfd->mBounds.width; } else { pfd->mSplittable = PR_TRUE; } } nscoord variableWidth = aLineBox.width - fixedWidth; if (variableWidth > 0) { // Each variable width frame gets a portion of the available extra // space that is proportional to the space it takes in the // line. The extra space is given to the frame by updating its // position and size. The frame is responsible for adjusting the // position of its contents on its own (during rendering). PRInt32 splittable = n - fixed; nscoord extraSpace = aMaxWidth - aLineBox.width; nscoord remainingExtra = extraSpace; nscoord dx = 0; float lineWidth = float(aLineBox.width); pfd = mFrameDataBase; for (i = 0; i < n; i++, pfd++) { nsRect r; nsIFrame* frame = pfd->mFrame; nsIHTMLReflow* ihr; if (NS_OK == frame->QueryInterface(kIHTMLReflowIID, (void**)&ihr)) { if (pfd->mSplittable && (pfd->mBounds.width > 0)) { float pctOfLine = float(pfd->mBounds.width) / lineWidth; nscoord extra = nscoord(pctOfLine * extraSpace); if (--splittable == 0) { extra = remainingExtra; } if (0 != extra) { nscoord used; ihr->AdjustFrameSize(extra, used); frame->GetRect(r); r.x += dx; frame->SetRect(r); dx += extra; } else if (0 != dx) { frame->GetRect(r); r.x += dx; frame->SetRect(r); } remainingExtra -= extra; } else if (0 != dx) { frame->GetRect(r); r.x += dx; frame->SetRect(r); } } } } }