/* -*- 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);
}
}
}
}
}