501 lines
17 KiB
C++
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;
|
|
}
|