Mozilla/mozilla/layout/html/base/src/nsInlineReflow.cpp
kipp%netscape.com 00cb1a62f1 added some nasty logging messages for frames that don't set max-element-size; added more vertical alignment noise
git-svn-id: svn://10.0.0.236/trunk@23183 18797224-902f-48f8-a5cc-f745e15eee43
1999-03-08 19:25:55 +00:00

1233 lines
39 KiB
C++

/* -*- 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 <b>inside</b> 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 <b>inside</b> 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<nsIFontMetrics> 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 <B>relative</B> 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);
}
}
}
}
}