Files
Mozilla/mozilla/layout/html/base/src/nsInlineLayout.cpp
kipp%netscape.com 6a52b74171 Removed css/layout directory
git-svn-id: svn://10.0.0.236/trunk@10015 18797224-902f-48f8-a5cc-f745e15eee43
1998-09-15 00:19:49 +00:00

501 lines
17 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 "nsInlineLayout.h"
#include "nsLineLayout.h"
#include "nsCSSLayout.h"
#include "nsHTMLIIDs.h"
#include "nsContainerFrame.h"
#include "nsIFontMetrics.h"
#include "nsIStyleContext.h"
#include "nsIPresContext.h"
#include "nsIReflowCommand.h"
#include "nsIRunaround.h"
#include "nsISpaceManager.h"
nsInlineLayout::nsInlineLayout(nsLineLayout& aLineLayout,
nsContainerFrame* aContainerFrame,
nsIStyleContext* aContainerStyle)
: mLineLayout(aLineLayout)
{
mContainerFrame = aContainerFrame;
mAscents = mAscentBuf;
mMaxAscents = sizeof(mAscentBuf) / sizeof(mAscentBuf[0]);
mContainerFont = (const nsStyleFont*)
aContainerStyle->GetStyleData(eStyleStruct_Font);
mContainerText = (const nsStyleText*)
aContainerStyle->GetStyleData(eStyleStruct_Text);
mContainerDisplay = (const nsStyleDisplay*)
aContainerStyle->GetStyleData(eStyleStruct_Display);
mDirection = mContainerDisplay->mDirection;
mIsBullet = PR_FALSE;
mHaveBullet = PR_FALSE;
mNextRCFrame = nsnull;
}
nsInlineLayout::~nsInlineLayout()
{
if (mAscents != mAscentBuf) {
delete [] mAscents;
}
}
void
nsInlineLayout::Init(const nsReflowState* aContainerReflowState)
{
mContainerReflowState = aContainerReflowState;
}
nsresult
nsInlineLayout::SetAscent(nscoord aAscent)
{
PRInt32 frameNum = mFrameNum;
if (frameNum == mMaxAscents) {
mMaxAscents *= 2;
nscoord* newAscents = new nscoord[mMaxAscents];
if (nsnull == newAscents) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsCRT::memcpy(newAscents, mAscents, sizeof(nscoord) * frameNum);
if (mAscents != mAscentBuf) {
delete [] mAscents;
}
mAscents = newAscents;
}
mAscents[frameNum] = aAscent;
return NS_OK;
}
void
nsInlineLayout::Prepare(PRBool aUnconstrainedWidth,
PRBool aNoWrap,
PRBool aComputeMaxElementSize)
{
mIsBullet = PR_FALSE;
mHaveBullet = PR_FALSE;
mFrameNum = 0;
mUnconstrainedWidth = aUnconstrainedWidth;
mNoWrap = aNoWrap;
mComputeMaxElementSize = aComputeMaxElementSize;
if (aComputeMaxElementSize) {
mMaxElementSize.width = 0;
mMaxElementSize.height = 0;
}
mMaxAscent = 0;
mMaxDescent = 0;
}
void
nsInlineLayout::SetReflowSpace(nscoord aX, nscoord aY,
nscoord aAvailWidth, nscoord aAvailHeight)
{
mAvailWidth = aAvailWidth;
mAvailHeight = aAvailHeight;
mX = aX;
mY = aY;
mLeftEdge = aX;
mRightEdge = aX + aAvailWidth;
}
//XXX block children of inline frames needs handling *here*
nsInlineReflowStatus
nsInlineLayout::ReflowAndPlaceFrame(nsIFrame* aFrame)
{
NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW,
("nsInlineLayout::ReflowAndPlaceFrame: frame=%p x=%d",
aFrame, mX));
// If the frame is a block frame and this is not the first frame on
// the line, we need to break before the block frame.
const nsStyleDisplay* kidDisplay;
aFrame->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&) kidDisplay);
const nsStylePosition* kidPosition;
aFrame->GetStyleData(eStyleStruct_Position,
(const nsStyleStruct*&) kidPosition);
PRBool isBlock = nsLineLayout::TreatFrameAsBlock(kidDisplay, kidPosition);
if (isBlock && !IsFirstChild()) {
return NS_INLINE_LINE_BREAK_BEFORE(0);/* XXX indicate never-reflowed? */
}
// Compute the maximum size of the frame. If there is no room at all
// for it, then trigger a line-break before the frame.
nsSize maxSize;
nsMargin margin;
if (!ComputeMaxSize(aFrame, margin, maxSize)) {
NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW,
("nsInlineLayout::ReflowAndPlaceFrame: break-before"));
return NS_INLINE_LINE_BREAK_BEFORE(0);/* XXX indicate never-reflowed? */
}
// Get reflow reason set correctly. It's possible that we created a
// child and then decided that we cannot reflow it (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
nsReflowState reflowState(aFrame, *mContainerReflowState, maxSize);
reflowState.reason = reason;
nsInlineReflowStatus rs;
nsSize frameMaxElementSize;
nsReflowMetrics metrics(mComputeMaxElementSize
? &frameMaxElementSize
: nsnull);
PRBool isAware;
aFrame->WillReflow(*mLineLayout.mPresContext);
rs = ReflowFrame(aFrame, metrics, reflowState, isAware);
if (NS_IS_REFLOW_ERROR(rs)) {
return rs;
}
if (NS_INLINE_IS_BREAK_BEFORE(rs)) {
return rs;
}
// XXX we need to do this because blocks depend on it; we shouldn't expect
// the child frame to deal with it.
if (eReflowReason_Initial == reason) {
aFrame->GetFrameState(state);
aFrame->SetFrameState(state & ~NS_FRAME_FIRST_REFLOW);
}
// XXX the 0,0 part of this is hack: get rid of it
if (!isAware && ((0 != metrics.width) || (0 != metrics.height))) {
mLineLayout.SetSkipLeadingWhiteSpace(PR_FALSE);
}
// It's possible the frame didn't fit
if (metrics.width > maxSize.width) {
if (!IsFirstChild()) {
// We are out of room.
// XXX mKidPrevInFlow
NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW,
("nsInlineLayout::ReflowAndPlaceFrame: !fit size=%d,%d",
metrics.width, metrics.height));
// XXX if the child requested a break and we need to break too...
return NS_INLINE_LINE_BREAK_BEFORE(0);
}
}
nsRect frameRect(mX, mY, metrics.width, metrics.height);
return PlaceFrame(aFrame, frameRect, metrics, margin, rs, isBlock);
}
// XXX RTL
PRBool
nsInlineLayout::IsFirstChild()
{
if (mHaveBullet) {
return mFrameNum < 2;
}
return mFrameNum < 1;
}
PRBool
nsInlineLayout::ComputeMaxSize(nsIFrame* aFrame,
nsMargin& aKidMargin,
nsSize& aResult)
{
const nsStyleSpacing* kidSpacing;
aFrame->GetStyleData(eStyleStruct_Spacing,
(const nsStyleStruct*&)kidSpacing);
kidSpacing->CalcMarginFor(aFrame, aKidMargin);
if (mUnconstrainedWidth || mNoWrap) {
aResult.width = NS_UNCONSTRAINEDSIZE;
}
else {
aResult.width = mRightEdge - mX;
aResult.width -= aKidMargin.left + aKidMargin.right;
// Note: We let zero sneak through here in case the next child is
// either something that gets compressed into zero or is a BR
// (which is zero width)
if (!IsFirstChild() && (aResult.width < 0)) {
// XXX Make sure child is dirty for next time
aFrame->WillReflow(*mLineLayout.mPresContext);
NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW,
("nsInlineLayout::ComputeMaxSize: !fit"));
return PR_FALSE;
}
}
// Give the child a limited height in case it's a block child and
// our height was limited.
aResult.height = mAvailHeight;
return PR_TRUE;
}
nsInlineReflowStatus
nsInlineLayout::ReflowFrame(nsIFrame* aKidFrame,
nsReflowMetrics& aMetrics,
const nsReflowState& aReflowState,
PRBool& aInlineAware)
{
// There are 3 ways to reflow the child frame: using the nsIRunaround
// interface, using the nsIInlineReflow interface or using the default
// Reflow method in nsIFrame. The order of precedence is nsIRunaround,
// nsIInlineReflow, nsIFrame. For all three API's we map the reflow status
// into an nsInlineReflowStatus.
NS_ASSERTION(nsnull != mLineLayout.mSpaceManager, "yikes");
nsresult rv;
nsIRunaround* runAround;
nsIInlineReflow* inlineReflow;
// Make local copies of x,y in case what we are reflowing is a
// floater and the floater ends up being a left aligned
// current-line-floater (which means it will adjust our mX value)
nscoord x = mX;
nscoord y = mY;
if ((nsnull != mLineLayout.mSpaceManager) &&
(NS_OK == aKidFrame->QueryInterface(kIRunaroundIID,
(void**)&runAround))) {
nsRect r;
mLineLayout.mSpaceManager->Translate(x, y);
runAround->ReflowAround(*mLineLayout.mPresContext,
mLineLayout.mSpaceManager,
aMetrics, aReflowState, r, rv);
mLineLayout.mSpaceManager->Translate(-x, -y);
aMetrics.width = r.width;
aMetrics.height = r.height;
aMetrics.ascent = r.height;
aMetrics.descent = 0;
aInlineAware = PR_FALSE;
}
else if (NS_OK == aKidFrame->QueryInterface(kIInlineReflowIID,
(void**)&inlineReflow)) {
mLineLayout.mSpaceManager->Translate(x, y);
rv = inlineReflow->InlineReflow(mLineLayout, aMetrics, aReflowState);
mLineLayout.mSpaceManager->Translate(-x, -y);
aInlineAware = PR_TRUE;
}
else {
mLineLayout.mSpaceManager->Translate(x, y);
aKidFrame->Reflow(*mLineLayout.mPresContext, aMetrics, aReflowState, rv);
mLineLayout.mSpaceManager->Translate(-x, -y);
aInlineAware = PR_FALSE;
}
if (NS_FRAME_IS_COMPLETE(rv)) {
nsIFrame* kidNextInFlow;
aKidFrame->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)
nsIFrame* parent;
aKidFrame->GetGeometricParent(parent);
((nsHTMLContainerFrame*)parent)->DeleteNextInFlowsFor(*mLineLayout.mPresContext,
aKidFrame);
}
}
NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW,
("nsInlineLayout::ReflowFrame: frame=%p reflowStatus=%x %saware",
aKidFrame, rv, aInlineAware ? "" :"not "));
return rv;
}
nsInlineReflowStatus
nsInlineLayout::PlaceFrame(nsIFrame* aFrame,
nsRect& aFrameRect,
const nsReflowMetrics& aFrameMetrics,
const nsMargin& aFrameMargin,
nsInlineReflowStatus aFrameReflowStatus,
PRBool aIsBlock)
{
nscoord horizontalMargins = 0;
// XXX RTL
// Special case to position outside list bullets.
if (mIsBullet) {
// We are placing the first child of the container and we have
// list-style-position of "outside" therefore this is the
// bullet that is being reflowed. The bullet is placed in the
// padding area of this block. Don't worry about getting the Y
// coordinate of the bullet right (vertical alignment will
// take care of that).
nsIFontMetrics* fm =
mLineLayout.mPresContext->GetMetricsFor(mContainerFont->mFont);
nscoord height;
fm->GetHeight(height);
nscoord dx = height / 2; // from old layout engine
NS_RELEASE(fm);
aFrameRect.x = mX - aFrameRect.width - dx;
if (aFrameRect.x < 0) aFrameRect.x = 0;
aFrame->SetRect(aFrameRect);
}
else {
// Place normal in-flow child
aFrame->SetRect(aFrameRect);
// Advance
const nsStyleDisplay* frameDisplay;
aFrame->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&) frameDisplay);
switch (frameDisplay->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:
horizontalMargins = aFrameMargin.left + aFrameMargin.right;
break;
}
mX += aFrameMetrics.width + horizontalMargins;
}
NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW,
("nsInlineLayout::PlaceFrame: frame=%p {%d, %d, %d, %d}",
aFrame,
aFrameRect.x, aFrameRect.y,
aFrameRect.width, aFrameRect.height));
// XXX this is not right; the max-element-size of a child depends on
// its margins which it doesn't know how to add in
// Fold in child's max-element-size information into our own
if (mComputeMaxElementSize) {
if (aFrameMetrics.maxElementSize->width > mMaxElementSize.width) {
mMaxElementSize.width = aFrameMetrics.maxElementSize->width;
}
if (aFrameMetrics.maxElementSize->height > mMaxElementSize.height) {
mMaxElementSize.height = aFrameMetrics.maxElementSize->height;
}
}
if (aFrameMetrics.ascent > mMaxAscent) {
mMaxAscent = aFrameMetrics.ascent;
}
if (aFrameMetrics.descent > mMaxDescent) {
mMaxDescent = aFrameMetrics.descent;
}
nsresult rv = SetAscent(aFrameMetrics.ascent);
if (NS_OK != rv) {
return nsInlineReflowStatus(rv);
}
mFrameNum++;
if (aIsBlock) {
if (!NS_INLINE_IS_BREAK(aFrameReflowStatus)) {
aFrameReflowStatus =
NS_INLINE_LINE_BREAK_AFTER(aFrameReflowStatus & NS_FRAME_NOT_COMPLETE);
}
}
return aFrameReflowStatus;
}
void
nsInlineLayout::AlignFrames(nsIFrame* aFrame, PRInt32 aFrameCount,
nsRect& aBounds)
{
NS_PRECONDITION(aFrameCount == mFrameNum, "bogus reflow");
// Vertically align the children on the line; this will compute
// the actual line height for us.
// XXX Fold in vertical alignment code here and make it update the
// max ascent and max descent values properly. When line-height is
// involved, give half of it to ascent and half to descent
nscoord lineHeight =
nsCSSLayout::VerticallyAlignChildren(mLineLayout.mPresContext,
mContainerFrame, mContainerFont,
mY, aFrame, aFrameCount,
mAscents, mMaxAscent/* XXX maxDescent */);
nscoord lineWidth = mX - mLeftEdge;
// Save away line bounds before other adjustments
aBounds.x = mLeftEdge;
aBounds.y = mY;
aBounds.width = lineWidth;
aBounds.height = lineHeight;
// Now horizontally place the children
if (!mUnconstrainedWidth && (mAvailWidth > lineWidth)) {
nsCSSLayout::HorizontallyPlaceChildren(mLineLayout.mPresContext,
mContainerFrame,
mContainerText->mTextAlign,
mDirection,
aFrame, aFrameCount,
lineWidth,
mAvailWidth);
}
// Last, apply relative positioning
nsCSSLayout::RelativePositionChildren(mLineLayout.mPresContext,
mContainerFrame,
aFrame, aFrameCount);
}
nsresult
nsInlineLayout::MaybeCreateNextInFlow(nsIFrame* aFrame,
nsIFrame*& aNextInFlowResult)
{
aNextInFlowResult = nsnull;
nsIFrame* nextInFlow;
aFrame->GetNextInFlow(nextInFlow);
if (nsnull == nextInFlow) {
// Create a continuation frame for the child frame and insert it
// into our lines child list.
nsIFrame* nextFrame;
aFrame->GetNextSibling(nextFrame);
nsIStyleContext* kidSC;
aFrame->GetStyleContext(mLineLayout.mPresContext, kidSC);
aFrame->CreateContinuingFrame(*mLineLayout.mPresContext, mContainerFrame,
kidSC, nextInFlow);
NS_RELEASE(kidSC);
if (nsnull == nextInFlow) {
return NS_ERROR_OUT_OF_MEMORY;
}
aFrame->SetNextSibling(nextInFlow);
nextInFlow->SetNextSibling(nextFrame);
NS_FRAME_LOG(NS_FRAME_TRACE_NEW_FRAMES,
("nsInlineLayout::MaybeCreateNextInFlow: frame=%p nextInFlow=%p",
aFrame, nextInFlow));
aNextInFlowResult = nextInFlow;
}
return NS_OK;
}