/* -*- 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 "nsInlineReflow.h" #include "nsLineLayout.h" #include "nsIFontMetrics.h" #include "nsIPresContext.h" #include "nsISpaceManager.h" #include "nsIStyleContext.h" #include "nsFrameReflowState.h" #include "nsHTMLContainerFrame.h" #include "nsHTMLIIDs.h" #include "nsStyleConsts.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 // XXX remove support for block reflow from this and move it into its // own class (nsBlockReflow?) nsInlineReflow::nsInlineReflow(nsLineLayout& aLineLayout, nsFrameReflowState& aOuterReflowState, nsHTMLContainerFrame* aOuterFrame, PRBool aOuterIsBlock) : mLineLayout(aLineLayout), mPresContext(aOuterReflowState.mPresContext), mOuterReflowState(aOuterReflowState), mComputeMaxElementSize(aOuterReflowState.mComputeMaxElementSize), mOuterIsBlock(aOuterIsBlock) { mSpaceManager = aLineLayout.mSpaceManager; NS_ASSERTION(nsnull != mSpaceManager, "caller must have space manager"); mOuterFrame = aOuterFrame; mFrameDataBase = mFrameDataBuf; mNumFrameData = sizeof(mFrameDataBuf) / sizeof(mFrameDataBuf[0]); } nsInlineReflow::~nsInlineReflow() { if (mFrameDataBase != mFrameDataBuf) { delete [] mFrameDataBase; } } void nsInlineReflow::Init(nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight) { #ifdef NS_DEBUG if ((aWidth > 100000) && (aWidth != NS_UNCONSTRAINEDSIZE)) { mOuterFrame->ListTag(stdout); printf(": Init: bad caller: width WAS %d(0x%x)\n", aWidth, aWidth); aWidth = NS_UNCONSTRAINEDSIZE; } if ((aHeight > 100000) && (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; mMaxElementSize.width = 0; mMaxElementSize.height = 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 > 100000) && (aWidth != NS_UNCONSTRAINEDSIZE)) { mOuterFrame->ListTag(stdout); printf(": UpdateBand: bad caller: width WAS %d(0x%x)\n", aWidth, aWidth); aWidth = NS_UNCONSTRAINEDSIZE; } if ((aHeight > 100000) && (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 == mOuterReflowState.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 } } const nsStyleDisplay* nsInlineReflow::GetDisplay() { if (nsnull != mDisplay) { return mDisplay; } mFrameData->mFrame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)mDisplay); return mDisplay; } const nsStylePosition* nsInlineReflow::GetPosition() { if (nsnull != mPosition) { return mPosition; } mFrameData->mFrame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&)mPosition); return mPosition; } const nsStyleSpacing* nsInlineReflow::GetSpacing() { if (nsnull != mSpacing) { return mSpacing; } mFrameData->mFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&)mSpacing); return mSpacing; } 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; mDisplay = nsnull; mSpacing = nsnull; mPosition = nsnull; return NS_OK; } nsresult nsInlineReflow::ReflowFrame(nsIFrame* aFrame, nsReflowStatus& aReflowStatus) { nsSize innerMaxElementSize; nsHTMLReflowMetrics metrics(mComputeMaxElementSize ? &innerMaxElementSize : nsnull); // Prepare for reflowing the frame nsresult rv = SetFrame(aFrame); if (NS_FAILED(rv)) { return rv; } // Next figure out how much available space there is for the frame. // Calculate raw margin values. PerFrameData* pfd = mFrameData; nsHTMLReflowState::ComputeMarginFor(pfd->mFrame, &mOuterReflowState, pfd->mMargin); // Apply top+left margins (as appropriate) to the frame computing // the new starting x,y coordinates for the frame. ApplyTopLeftMargins(); // Compute the available area to reflow the frame into. if (!ComputeAvailableSize()) { aReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); return NS_OK; } // Reflow the frame. If the frame must be placed somewhere else // then we return immediately. if (ReflowFrame(metrics, aReflowStatus)) { // See if we can place the frame. If we can't fit it, then we // return now. if (CanPlaceFrame(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; } // XXX doesn't apply top margin void nsInlineReflow::ApplyTopLeftMargins() { 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; const nsStyleDisplay* display = GetDisplay(); switch (display->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; } } PRBool nsInlineReflow::ComputeAvailableSize() { PerFrameData* pfd = mFrameData; // Compute the available size from the outer's perspective if (NS_UNCONSTRAINEDSIZE == mRightEdge) { mFrameAvailSize.width = NS_UNCONSTRAINEDSIZE; } else { // XXX What does a negative right margin mean here? mFrameAvailSize.width = mRightEdge - pfd->mBounds.x - pfd->mMargin.right; } if (NS_UNCONSTRAINEDSIZE == mBottomEdge) { mFrameAvailSize.height = NS_UNCONSTRAINEDSIZE; } else { mFrameAvailSize.height = mBottomEdge - pfd->mBounds.y - pfd->mMargin.bottom; } if (mOuterReflowState.mNoWrap) { mFrameAvailSize.width = mOuterReflowState.maxSize.width; return PR_TRUE; } #if XXX // Give up now if there is no chance. Note that we allow a reflow if // the available space is zero because that way things that end up // zero sized won't trigger a new line to be created. We also allow // a reflow if we can't break before this frame. mInWord = mLineLayout.InWord(); if (!mInWord && mCanBreakBeforeFrame && ((mFrameAvailSize.width < 0) || (mFrameAvailSize.height < 0))) { return PR_FALSE; } #endif return PR_TRUE; } /** * Reflow the frame, choosing the appropriate reflow method. */ PRBool nsInlineReflow::ReflowFrame(nsHTMLReflowMetrics& aMetrics, nsReflowStatus& aStatus) { PerFrameData* pfd = mFrameData; // 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; nsIFrame* frame = pfd->mFrame; nsFrameState state; frame->GetFrameState(state); if (NS_FRAME_FIRST_REFLOW & state) { reason = eReflowReason_Initial; } else if (mOuterReflowState.mNextRCFrame == frame) { reason = eReflowReason_Incremental; // Make sure we only incrementally reflow once mOuterReflowState.mNextRCFrame = nsnull; } // Setup reflow state for reflowing the frame nsHTMLReflowState reflowState(mPresContext, frame, mOuterReflowState, mFrameAvailSize); reflowState.lineLayout = &mLineLayout; reflowState.reason = reason; mLineLayout.SetUnderstandsWhiteSpace(PR_FALSE); // 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(); // Let frame know that are reflowing it nscoord x = pfd->mBounds.x; nscoord y = pfd->mBounds.y; nsIHTMLReflow* htmlReflow; frame->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. nscoord tx = x - mOuterReflowState.mBorderPadding.left; nscoord ty = y - mOuterReflowState.mBorderPadding.top; mSpaceManager->Translate(tx, ty); frame->MoveTo(x, y); htmlReflow->Reflow(mPresContext, aMetrics, reflowState, aStatus); // XXX could return this in aStatus to save a few cycles // XXX could mandate that child sets up mCombinedArea too... frame->GetFrameState(state); if (NS_FRAME_OUTSIDE_CHILDREN & state) { pfd->mCombinedArea = aMetrics.mCombinedArea; } else { pfd->mCombinedArea.x = 0; pfd->mCombinedArea.y = 0; pfd->mCombinedArea.width = aMetrics.width; pfd->mCombinedArea.height = aMetrics.height; } pfd->mBounds.width = aMetrics.width; pfd->mBounds.height = aMetrics.height; mSpaceManager->Translate(-tx, -ty); // 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) { frame->GetFrameState(state); frame->SetFrameState(state & ~NS_FRAME_FIRST_REFLOW); } if (!NS_INLINE_IS_BREAK_BEFORE(aStatus)) { // 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(aStatus)) { nsIFrame* kidNextInFlow; frame->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; frame->GetGeometricParent((nsIFrame*&) parent); parent->DeleteChildsNextInFlow(mPresContext, frame); } } } NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsInlineReflow::ReflowFrame: frame=%p reflowStatus=%x %s", frame, aStatus, mLineLayout.GetUnderstandsWhiteSpace() ? "UWS" : "!UWS")); return !NS_INLINE_IS_BREAK_BEFORE(aStatus); } /** * 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(nsHTMLReflowMetrics& aMetrics, nsReflowStatus& aStatus) { PerFrameData* pfd = mFrameData; // Compute right margin to use mRightMargin = 0; if (0 != pfd->mBounds.width) { const nsStyleDisplay* display = GetDisplay(); switch (display->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 || mOuterReflowState.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); // Update 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 = aMetrics.maxElementSize->width + pfd->mMargin.left + pfd->mMargin.right; if (mw > mMaxElementSize.width) { mMaxElementSize.width = mw; } // XXX take into account top/bottom margins nscoord mh = aMetrics.maxElementSize->height; if (mh > mMaxElementSize.height) { mMaxElementSize.height = mh; } } } } // XXX what about ebina's center vs. ncsa-center? 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); nsIFontMetrics* fm = mPresContext.GetMetricsFor(font->mFont); #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; nscoord height = pfd->mBounds.height + pfd->mMargin.top + pfd->mMargin.bottom; pfd->mAscent += pfd->mMargin.top; 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 (height > maxTopHeight) { maxTopHeight = height; } continue; case NS_STYLE_VERTICAL_ALIGN_BOTTOM: if (height > maxBottomHeight) { maxBottomHeight = height; } continue; case NS_STYLE_VERTICAL_ALIGN_MIDDLE: // Align the midpoint of the frame with 1/2 the parents x-height fm->GetXHeight(fontParam); yTop = -(fontParam / 2) - (pfd->mBounds.height/2); break; case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM: fm->GetMaxDescent(fontParam); yTop = fontParam - pfd->mBounds.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. Since Y coordinates // increase towards the bottom of the screen we reverse the // sign. All of the raising and lowering is done relative to the // baseline, so we start our adjustments there. yTop = -pfd->mAscent - textStyle->mVerticalAlign.GetCoordValue(); break; case eStyleUnit_Percent: // The percentage is relative to the line-height of the element // itself. The line-height will be the final height of the // inline element (CSS2 10.8.1 says that the line-height defines // the precise height of inline non-replaced elements). yTop = -pfd->mAscent - nscoord(textStyle->mVerticalAlign.GetPercentValue() * pfd->mBounds.height); break; default: yTop = -pfd->mAscent; break; } pfd->mBounds.y = yTop; #ifdef NOISY_VERTICAL_ALIGN printf(" "); nsAutoString tmp; pfd->mFrame->GetFrameName(tmp); fputs(tmp, stdout); printf(": yTop=%d minYTop=%d yBottom=%d maxYBottom=%d\n", yTop, minYTop, yTop + height, maxYBottom); NS_ASSERTION(yTop >= -1000000, "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) { nscoord newLineHeight = CalcLineHeightFor(mPresContext, mOuterFrame, lineHeight); // If the newLineHeight is larger then just use; otherwise if the // outer frame is an inline frame then use it as well (line-height // on block frames specify the minimal height while on inline // frames it specifies the precise height). if (mOuterIsBlock) {/* XXX temporary until line-height inheritance issue is resolved */ if ((newLineHeight > lineHeight) || !mOuterIsBlock) { topEdge += (newLineHeight - lineHeight) / 2; lineHeight = newLineHeight; #ifdef NOISY_VERTICAL_ALIGN printf(" *lineHeight=newLineHeight=%d topEdgeDelta=%d\n", lineHeight, topEdge - mTopEdge); #endif } } } aLineBox.height = lineHeight; // Pass2 - position each of the frames for (pfd = pfd0; pfd < end; pfd++) { nsIFrame* frame = pfd->mFrame; 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: // XXX negative top margins on these will do weird things, maybe? pfd->mBounds.y = mTopEdge + pfd->mMargin.top; break; case NS_STYLE_VERTICAL_ALIGN_BOTTOM: pfd->mBounds.y = mTopEdge + lineHeight - pfd->mBounds.height; break; default: pfd->mBounds.y = topEdge + maxAscent + pfd->mBounds.y + pfd->mMargin.top; break; } } else { pfd->mBounds.y = topEdge + maxAscent + pfd->mBounds.y + pfd->mMargin.top; } frame->SetRect(pfd->mBounds); } aMaxAscent = maxAscent; aMaxDescent = lineHeight - maxAscent; NS_RELEASE(fm); } void nsInlineReflow::HorizontalAlignFrames(nsRect& aLineBox, PRBool aAllowJustify) { // Before we start, trim any trailing whitespace off of the last // frame in the line. 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; } } const nsStyleText* styleText = mOuterReflowState.mStyleText; nscoord maxWidth = mRightEdge - mLeftEdge; if (aLineBox.width < maxWidth) { nscoord dx = 0; switch (styleText->mTextAlign) { case NS_STYLE_TEXT_ALIGN_DEFAULT: if (NS_STYLE_DIRECTION_LTR == mOuterReflowState.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 == mOuterReflowState.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 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); } } } } 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); nscoord dx = 0; switch (kidPosition->mLeftOffset.GetUnit()) { case eStyleUnit_Percent: printf("XXX: not yet implemented: % relative position\n"); case eStyleUnit_Auto: break; case eStyleUnit_Coord: dx = kidPosition->mLeftOffset.GetCoordValue(); break; } nscoord dy = 0; switch (kidPosition->mTopOffset.GetUnit()) { case eStyleUnit_Percent: printf("XXX: not yet implemented: % relative position\n"); case eStyleUnit_Auto: break; case eStyleUnit_Coord: dy = kidPosition->mTopOffset.GetCoordValue(); 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; } // XXX performance todo: this computation can be cached, // but not in the style-context nscoord nsInlineReflow::CalcLineHeightFor(nsIPresContext& aPresContext, nsIFrame* aFrame, nscoord aBaseLineHeight) { nscoord lineHeight = aBaseLineHeight; nsIStyleContext* sc; aFrame->GetStyleContext(sc); const nsStyleFont* elementFont = nsnull; if (nsnull != sc) { elementFont = (const nsStyleFont*)sc->GetStyleData(eStyleStruct_Font); for (;;) { const nsStyleText* text = (const nsStyleText*) sc->GetStyleData(eStyleStruct_Text); if (nsnull != text) { nsStyleUnit unit = text->mLineHeight.GetUnit(); #ifdef NOISY_VERTICAL_ALIGN printf(" styleUnit=%d\n", unit); #endif if (eStyleUnit_Enumerated == unit) { // Normal value; we use 1.0 for normal // XXX could come from somewhere else break; } else if (eStyleUnit_Factor == unit) { if (nsnull != elementFont) { // CSS2 spec says that the number is inherited, not the // computed value. Therefore use the font size of the // element times the inherited number. nscoord size = elementFont->mFont.size; lineHeight = nscoord(size * text->mLineHeight.GetFactorValue()); } break; } else if (eStyleUnit_Coord == unit) { lineHeight = text->mLineHeight.GetCoordValue(); // CSS2 spec 10.8.1: negative length values are illegal. if (lineHeight < 0) { lineHeight = aBaseLineHeight; } break; } else if (eStyleUnit_Percent == unit) { // XXX This could arguably be the font-metrics actual height // instead since the spec says use the computed height. const nsStyleFont* font = (const nsStyleFont*) sc->GetStyleData(eStyleStruct_Font); nscoord size = font->mFont.size; lineHeight = nscoord(size * text->mLineHeight.GetPercentValue()); break; } else if (eStyleUnit_Inherit == unit) { nsIStyleContext* parentSC; parentSC = sc->GetParent(); if (nsnull == parentSC) { // Note: Break before releasing to avoid double-releasing sc break; } NS_RELEASE(sc); sc = parentSC; } else { // other units are not part of the spec so don't bother // looping break; } } } NS_RELEASE(sc); } return lineHeight; } 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); } } } } }