зеркало из https://github.com/mozilla/pjs.git
2126 строки
69 KiB
C
2126 строки
69 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 "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
#include "nsBlockFrame.h"
|
|
#include "nsSize.h"
|
|
#include "nsIAnchoredItems.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIContentDelegate.h"
|
|
#include "nsISpaceManager.h"
|
|
#include "nsIStyleContext.h"
|
|
#include "nsStyleConsts.h"
|
|
#include "nsIPresContext.h"
|
|
#include "nsMargin.h"
|
|
#include "nsHTMLIIDs.h"
|
|
#include "nsCSSLayout.h"
|
|
#include "nsCRT.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsReflowCommand.h"
|
|
#include "nsPlaceholderFrame.h"
|
|
#include "nsHTMLAtoms.h"
|
|
#include "nsHTMLValue.h"
|
|
#include "nsIHTMLContent.h"
|
|
|
|
#ifdef NS_DEBUG
|
|
#undef NOISY
|
|
#undef NOISY_FLOW
|
|
#else
|
|
#undef NOISY
|
|
#undef NOISY_FLOW
|
|
#endif
|
|
|
|
static NS_DEFINE_IID(kIRunaroundIID, NS_IRUNAROUND_IID);
|
|
static NS_DEFINE_IID(kIFloaterContainerIID, NS_IFLOATERCONTAINER_IID);
|
|
static NS_DEFINE_IID(kIAnchoredItemsIID, NS_IANCHOREDITEMS_IID);
|
|
static NS_DEFINE_IID(kStyleMoleculeSID, NS_STYLEMOLECULE_SID);
|
|
static NS_DEFINE_IID(kStyleFontSID, NS_STYLEFONT_SID);
|
|
|
|
struct BlockBandData : public nsBandData {
|
|
nsRect data[5];
|
|
|
|
BlockBandData() {size = 5; rects = data;}
|
|
};
|
|
|
|
// XXX Bugs
|
|
// 1. right to left reflow can generate negative x coordinates.
|
|
|
|
// XXX Speedup idea (all containers)
|
|
|
|
// If I reflow a child and it gives back not-complete status then
|
|
// there is no sense in trying to pullup children. For blocks, it's a
|
|
// little more complicated unless the child is a block - if the child
|
|
// is a block, then we must be out of room hence we should stop. If
|
|
// the child is not a block then our line should be flushed (see #2
|
|
// below) if our line is already empty then we must be out of room.
|
|
|
|
// For inline frames and column frames, if we reflow a child and get
|
|
// back not-complete status then we should bail immediately because we
|
|
// are out of room.
|
|
|
|
// XXX Speedup ideas:
|
|
// 1. change pullup code to use line information from next in flow
|
|
// 2. we can advance to next line immediately after reflowing something
|
|
// and noticing that it's not complete.
|
|
// 3. pass down last child information in aState so that pullup, etc.,
|
|
// don't need to recompute it
|
|
|
|
// XXX TODO:
|
|
// 0. Move justification into line flushing code
|
|
|
|
// 1. To get ebina margins I need "auto" information from the style
|
|
// system margin's. A bottom/top margin of auto will then be computed like
|
|
// ebina computes it [however the heck that is].
|
|
|
|
// 2. kicking out floaters and talking with floater container to adjust
|
|
// left and right margins
|
|
|
|
nsBlockReflowState::nsBlockReflowState()
|
|
{
|
|
}
|
|
|
|
void nsBlockReflowState::Init(const nsSize& aMaxSize,
|
|
nsSize* aMaxElementSize,
|
|
nsStyleFont* aFont,
|
|
nsStyleMolecule* aMol,
|
|
nsISpaceManager* aSpaceManager)
|
|
{
|
|
firstLine = PR_TRUE;
|
|
allowLeadingWhitespace = PR_FALSE;
|
|
breakAfterChild = PR_FALSE;
|
|
breakBeforeChild = PR_FALSE;
|
|
firstChildIsInsideBullet = PR_FALSE;
|
|
nextListOrdinal = -1;
|
|
column = 0;
|
|
|
|
spaceManager = aSpaceManager;
|
|
currentBand = new BlockBandData;
|
|
font = aFont;
|
|
mol = aMol;
|
|
availSize.width = aMaxSize.width;
|
|
availSize.height = aMaxSize.height;
|
|
maxElementSize = aMaxElementSize;
|
|
if (nsnull != aMaxElementSize) {
|
|
aMaxElementSize->width = 0;
|
|
aMaxElementSize->height = 0;
|
|
}
|
|
|
|
kidXMost = 0;
|
|
x = 0;
|
|
y = 0;
|
|
|
|
isInline = PR_FALSE;
|
|
currentLineNumber = 0;
|
|
lineStart = nsnull;
|
|
lineLength = 0;
|
|
ascents = ascentBuf;
|
|
maxAscent = 0;
|
|
maxDescent = 0;
|
|
lineWidth = 0;
|
|
maxPosBottomMargin = 0;
|
|
maxNegBottomMargin = 0;
|
|
lineMaxElementSize.width = 0;
|
|
lineMaxElementSize.height = 0;
|
|
lastContentIsComplete = PR_TRUE;
|
|
|
|
maxAscents = sizeof(ascentBuf) / sizeof(ascentBuf[0]);
|
|
needRelativePos = PR_FALSE;
|
|
|
|
prevLineLastFrame = nsnull;
|
|
prevLineHeight = 0;
|
|
topMargin = 0;
|
|
prevMaxPosBottomMargin = 0;
|
|
prevMaxNegBottomMargin = 0;
|
|
prevLineLastContentIsComplete = PR_TRUE;
|
|
|
|
unconstrainedWidth = PRBool(aMaxSize.width == NS_UNCONSTRAINEDSIZE);
|
|
unconstrainedHeight = PRBool(aMaxSize.height == NS_UNCONSTRAINEDSIZE);
|
|
|
|
justifying = (NS_STYLE_TEXT_ALIGN_JUSTIFY == mol->textAlign) &&
|
|
(NS_STYLE_WHITESPACE_PRE != mol->whiteSpace);
|
|
reflowStatus = nsIFrame::frNotComplete;
|
|
}
|
|
|
|
nsBlockReflowState::~nsBlockReflowState()
|
|
{
|
|
if (ascents != ascentBuf) {
|
|
delete ascents;
|
|
}
|
|
delete currentBand;
|
|
}
|
|
|
|
void nsBlockReflowState::AddAscent(nscoord aAscent)
|
|
{
|
|
NS_PRECONDITION(lineLength <= maxAscents, "bad line length");
|
|
if (lineLength == maxAscents) {
|
|
maxAscents *= 2;
|
|
nscoord* newAscents = new nscoord[maxAscents];
|
|
if (nsnull != newAscents) {
|
|
nsCRT::memcpy(newAscents, ascents, sizeof(nscoord) * lineLength);
|
|
if (ascents != ascentBuf) {
|
|
delete ascents;
|
|
}
|
|
ascents = newAscents;
|
|
} else {
|
|
// Yikes! Out of memory!
|
|
return;
|
|
}
|
|
}
|
|
ascents[lineLength] = aAscent;
|
|
}
|
|
|
|
void nsBlockReflowState::AdvanceToNextLine(nsIFrame* aPrevLineLastFrame,
|
|
nscoord aPrevLineHeight)
|
|
{
|
|
firstLine = PR_FALSE;
|
|
allowLeadingWhitespace = PR_FALSE;
|
|
column = 0;
|
|
breakAfterChild = PR_FALSE;
|
|
breakBeforeChild = PR_FALSE;
|
|
lineStart = nsnull;
|
|
lineLength = 0;
|
|
currentLineNumber++;
|
|
maxAscent = 0;
|
|
maxDescent = 0;
|
|
lineWidth = 0;
|
|
needRelativePos = PR_FALSE;
|
|
|
|
prevLineLastFrame = aPrevLineLastFrame;
|
|
prevLineHeight = aPrevLineHeight;
|
|
prevMaxPosBottomMargin = maxPosBottomMargin;
|
|
prevMaxNegBottomMargin = maxNegBottomMargin;
|
|
|
|
// Remember previous line's lastContentIsComplete
|
|
prevLineLastContentIsComplete = lastContentIsComplete;
|
|
lastContentIsComplete = PR_TRUE;
|
|
|
|
topMargin = 0;
|
|
maxPosBottomMargin = 0;
|
|
maxNegBottomMargin = 0;
|
|
}
|
|
|
|
#ifdef NS_DEBUG
|
|
void nsBlockReflowState::DumpLine()
|
|
{
|
|
nsIFrame* f = lineStart;
|
|
PRInt32 ll = lineLength;
|
|
while (--ll >= 0) {
|
|
printf(" ");
|
|
((nsFrame*)f)->ListTag(stdout);/* XXX */
|
|
printf("\n");
|
|
f->GetNextSibling(f);
|
|
}
|
|
}
|
|
|
|
void nsBlockReflowState::DumpList()
|
|
{
|
|
nsIFrame* f = lineStart;
|
|
while (nsnull != f) {
|
|
printf(" ");
|
|
((nsFrame*)f)->ListTag(stdout);/* XXX */
|
|
printf("\n");
|
|
f->GetNextSibling(f);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nsresult nsBlockFrame::NewFrame(nsIFrame** aInstancePtrResult,
|
|
nsIContent* aContent,
|
|
PRInt32 aIndexInParent,
|
|
nsIFrame* aParent)
|
|
{
|
|
NS_PRECONDITION(nsnull != aInstancePtrResult, "null ptr");
|
|
if (nsnull == aInstancePtrResult) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
nsIFrame* it = new nsBlockFrame(aContent, aIndexInParent, aParent);
|
|
if (nsnull == it) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
*aInstancePtrResult = it;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsBlockFrame::nsBlockFrame(nsIContent* aContent,
|
|
PRInt32 aIndexInParent,
|
|
nsIFrame* aParent)
|
|
: nsHTMLContainerFrame(aContent, aIndexInParent, aParent)
|
|
{
|
|
}
|
|
|
|
nsBlockFrame::~nsBlockFrame()
|
|
{
|
|
if (nsnull != mLines) {
|
|
delete mLines;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsBlockFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
|
|
{
|
|
NS_PRECONDITION(0 != aInstancePtr, "null ptr");
|
|
if (NULL == aInstancePtr) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
if (aIID.Equals(kIHTMLFrameTypeIID)) {
|
|
*aInstancePtr = (void*) ((nsIHTMLFrameType*) this);
|
|
return NS_OK;
|
|
} else if (aIID.Equals(kIRunaroundIID)) {
|
|
*aInstancePtr = (void*) ((nsIRunaround*) this);
|
|
return NS_OK;
|
|
} else if (aIID.Equals(kIFloaterContainerIID)) {
|
|
*aInstancePtr = (void*) ((nsIFloaterContainer*) this);
|
|
return NS_OK;
|
|
}
|
|
return nsHTMLContainerFrame::QueryInterface(aIID, aInstancePtr);
|
|
}
|
|
|
|
// Computes the top margin to use for this child frames based on its display
|
|
// type and the display type of the previous child frame.
|
|
//
|
|
// Adjacent vertical margins between block-level elements are collapsed.
|
|
nscoord nsBlockFrame::GetTopMarginFor(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState,
|
|
nsIFrame* aKidFrame,
|
|
nsStyleMolecule* aKidMol,
|
|
PRBool aIsInline)
|
|
{
|
|
if (aIsInline) {
|
|
// Just use whatever the previous bottom margin was
|
|
return aState.prevMaxPosBottomMargin - aState.prevMaxNegBottomMargin;
|
|
} else {
|
|
// Does the frame have a prev-in-flow?
|
|
nsIFrame* kidPrevInFlow;
|
|
|
|
aKidFrame->GetPrevInFlow(kidPrevInFlow);
|
|
|
|
if (nsnull == kidPrevInFlow) {
|
|
nscoord maxNegTopMargin = 0;
|
|
nscoord maxPosTopMargin = 0;
|
|
if (aKidMol->margin.top < 0) {
|
|
maxNegTopMargin = -aKidMol->margin.top;
|
|
} else {
|
|
maxPosTopMargin = aKidMol->margin.top;
|
|
}
|
|
|
|
nscoord maxPos = PR_MAX(aState.prevMaxPosBottomMargin, maxPosTopMargin);
|
|
nscoord maxNeg = PR_MAX(aState.prevMaxNegBottomMargin, maxNegTopMargin);
|
|
return maxPos - maxNeg;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsBlockFrame::PlaceBelowCurrentLineFloaters(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState,
|
|
nscoord aY)
|
|
{
|
|
NS_PRECONDITION(aState.floaterToDo.Count() > 0, "no floaters");
|
|
|
|
// XXX Factor this code with PlaceFloater()...
|
|
PRInt32 numFloaters = aState.floaterToDo.Count();
|
|
|
|
for (PRInt32 i = 0; i < numFloaters; i++) {
|
|
nsIFrame* floater = (nsIFrame*)aState.floaterToDo[i];
|
|
nsRect region;
|
|
|
|
// Get the band of available space
|
|
// XXX This is inefficient to do this inside the loop...
|
|
GetAvailableSpaceBand(aState, aY);
|
|
|
|
// Get the type of floater
|
|
nsIStyleContext* styleContext;
|
|
|
|
floater->GetStyleContext(aCX, styleContext);
|
|
nsStyleMolecule* mol = (nsStyleMolecule*)styleContext->GetData(kStyleMoleculeSID);
|
|
NS_RELEASE(styleContext);
|
|
|
|
floater->GetRect(region);
|
|
region.y = mCurrentState->currentBand->rects[0].y;
|
|
|
|
if (NS_STYLE_FLOAT_LEFT == mol->floats) {
|
|
region.x = mCurrentState->currentBand->rects[0].x;
|
|
} else {
|
|
NS_ASSERTION(NS_STYLE_FLOAT_RIGHT == mol->floats, "bad float type");
|
|
region.x = mCurrentState->currentBand->rects[0].XMost() - region.width;
|
|
}
|
|
|
|
// XXX Don't forget the floater's margins...
|
|
mCurrentState->spaceManager->Translate(mCurrentState->borderPadding.left, 0);
|
|
mCurrentState->spaceManager->AddRectRegion(region);
|
|
|
|
// Set the origin of the floater in world coordinates
|
|
nscoord worldX, worldY;
|
|
|
|
mCurrentState->spaceManager->GetTranslation(worldX, worldY);
|
|
floater->MoveTo(region.x + worldX, region.y + worldY);
|
|
mCurrentState->spaceManager->Translate(-mCurrentState->borderPadding.left, 0);
|
|
}
|
|
aState.floaterToDo.Clear();
|
|
}
|
|
|
|
/**
|
|
* Flush a line out. Return true if the line fits in our available
|
|
* height. If the line does not fit then return false. When the line
|
|
* fits we advance the y coordinate, reset the x coordinate and
|
|
* prepare the nsBlockReflowState for the next line.
|
|
*/
|
|
PRBool nsBlockFrame::AdvanceToNextLine(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState)
|
|
{
|
|
NS_PRECONDITION(aState.lineLength > 0, "bad line");
|
|
NS_PRECONDITION(nsnull != aState.lineStart, "bad line");
|
|
|
|
nscoord y = aState.y + aState.topMargin;
|
|
nscoord lineHeight;
|
|
|
|
if (aState.isInline) {
|
|
// Vertically align the children on this line, returning the height of
|
|
// the line upon completion.
|
|
lineHeight = nsCSSLayout::VerticallyAlignChildren(aCX, this, aState.font, y,
|
|
aState.lineStart, aState.lineLength,
|
|
aState.ascents, aState.maxAscent);
|
|
|
|
// Any below current line floaters to place?
|
|
if (aState.floaterToDo.Count() > 0) {
|
|
PlaceBelowCurrentLineFloaters(aCX, aState, y + lineHeight);
|
|
// XXX Factor in the height of the floaters as well when considering
|
|
// whether the line fits.
|
|
// The default policy is that if there isn't room for the floaters then
|
|
// both the line and the floaters are pushed to the next-in-flow...
|
|
}
|
|
} else {
|
|
nsSize size;
|
|
|
|
aState.lineStart->GetSize(size);
|
|
lineHeight = size.height;
|
|
}
|
|
|
|
// The first line always fits
|
|
if (aState.currentLineNumber > 0) {
|
|
nscoord yb = aState.borderPadding.top + aState.availSize.height;
|
|
if (y + lineHeight > yb) {
|
|
// After vertical alignment of the children and factoring in the
|
|
// proper margin, the line doesn't fit.
|
|
return PR_FALSE;
|
|
}
|
|
}
|
|
|
|
if (aState.isInline) {
|
|
// Check if the right-edge of the line exceeds our running x-most
|
|
nscoord xMost = aState.borderPadding.left + aState.lineWidth;
|
|
if (xMost > aState.kidXMost) {
|
|
aState.kidXMost = xMost;
|
|
}
|
|
}
|
|
|
|
// Advance the y coordinate to the new position where the next
|
|
// line or block element will go.
|
|
aState.y = y + lineHeight;
|
|
aState.x = 0;
|
|
|
|
// Now that the vertical alignment is done we can perform horizontal
|
|
// alignment and relative positioning. Skip all of these if we are
|
|
// doing an unconstrained (in x) reflow. There's no point in doing
|
|
// the work if we *know* we are going to reflowed again.
|
|
if (!aState.unconstrainedWidth) {
|
|
nsCSSLayout::HorizontallyPlaceChildren(aCX, this, aState.mol,
|
|
aState.lineStart, aState.lineLength,
|
|
aState.lineWidth,
|
|
aState.availSize.width);
|
|
|
|
// Finally, now that the in-flow positions of the line's frames are
|
|
// known we can apply relative positioning if any of them need it.
|
|
if (!aState.justifying) {
|
|
nsCSSLayout::RelativePositionChildren(aCX, this, aState.mol,
|
|
aState.lineStart,
|
|
aState.lineLength);
|
|
}
|
|
}
|
|
|
|
// Record line length
|
|
aState.lineLengths.AppendElement((void*)aState.lineLength);
|
|
|
|
// Find the last frame in the line
|
|
// XXX keep this as running state in the nsBlockReflowState
|
|
nsIFrame* lastFrame = aState.lineStart;
|
|
PRInt32 lineLen = aState.lineLength - 1;
|
|
while (--lineLen >= 0) {
|
|
lastFrame->GetNextSibling(lastFrame);
|
|
}
|
|
|
|
// Update maxElementSize
|
|
if (nsnull != aState.maxElementSize) {
|
|
nsSize& lineMax = aState.lineMaxElementSize;
|
|
nsSize* maxMax = aState.maxElementSize;
|
|
if (lineMax.width > maxMax->width) {
|
|
maxMax->width = lineMax.width;
|
|
}
|
|
if (lineMax.height > maxMax->height) {
|
|
maxMax->height = lineMax.height;
|
|
}
|
|
aState.lineMaxElementSize.width = 0;
|
|
aState.lineMaxElementSize.height = 0;
|
|
}
|
|
|
|
// Advance to the next line
|
|
aState.AdvanceToNextLine(lastFrame, lineHeight);
|
|
|
|
return PR_TRUE;
|
|
}
|
|
|
|
/**
|
|
* Add an inline child to the current line. Advance various running
|
|
* values after placement.
|
|
*/
|
|
void nsBlockFrame::AddInlineChildToLine(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState,
|
|
nsIFrame* aKidFrame,
|
|
nsReflowMetrics& aKidSize,
|
|
nsSize* aKidMaxElementSize,
|
|
nsStyleMolecule* aKidMol)
|
|
{
|
|
NS_PRECONDITION(nsnull != aState.lineStart, "bad line");
|
|
|
|
PRIntn direction = aState.mol->direction;
|
|
if (NS_STYLE_POSITION_RELATIVE == aKidMol->positionFlags) {
|
|
aState.needRelativePos = PR_TRUE;
|
|
}
|
|
|
|
// Place and size the child
|
|
// XXX add in left margin from kid
|
|
nsRect r;
|
|
r.y = aState.y;
|
|
r.width = aKidSize.width;
|
|
r.height = aKidSize.height;
|
|
if (NS_STYLE_DIRECTION_LTR == aState.mol->direction) {
|
|
// Left to right positioning.
|
|
r.x = aState.borderPadding.left + aState.x + aKidMol->margin.left;
|
|
aState.x += aKidSize.width + aKidMol->margin.left + aKidMol->margin.right;
|
|
} else {
|
|
// Right to left positioning
|
|
// XXX what should we do when aState.x goes negative???
|
|
r.x = aState.x - aState.borderPadding.right - aKidMol->margin.right - aKidSize.width;
|
|
aState.x -= aKidSize.width + aKidMol->margin.right + aKidMol->margin.left;
|
|
}
|
|
aKidFrame->SetRect(r);
|
|
aState.AddAscent(aKidSize.ascent);
|
|
aState.lineWidth += aKidSize.width;
|
|
aState.lineLength++;
|
|
|
|
// Update maximums for the line
|
|
if (aKidSize.ascent > aState.maxAscent) {
|
|
aState.maxAscent = aKidSize.ascent;
|
|
}
|
|
if (aKidSize.descent > aState.maxDescent) {
|
|
aState.maxDescent = aKidSize.descent;
|
|
}
|
|
// Update running margin maximums
|
|
if (aState.firstChildIsInsideBullet && (aKidFrame == mFirstChild)) {
|
|
// XXX temporary code. Since the molecule for the bullet frame
|
|
// is the same as the LI frame, we get bad style information.
|
|
// ignore it.
|
|
} else {
|
|
nscoord margin;
|
|
#if 0
|
|
// XXX CSS2 spec says that top/bottom margin don't affect line height
|
|
// calculation. We're waiting for clarification on this issue...
|
|
if ((margin = aKidMol->margin.top) < 0) {
|
|
margin = -margin;
|
|
if (margin > aState.maxNegTopMargin) {
|
|
aState.maxNegTopMargin = margin;
|
|
}
|
|
} else {
|
|
if (margin > aState.maxPosTopMargin) {
|
|
aState.maxPosTopMargin = margin;
|
|
}
|
|
}
|
|
#endif
|
|
if ((margin = aKidMol->margin.bottom) < 0) {
|
|
margin = -margin;
|
|
if (margin > aState.maxNegBottomMargin) {
|
|
aState.maxNegBottomMargin = margin;
|
|
}
|
|
} else {
|
|
if (margin > aState.maxPosBottomMargin) {
|
|
aState.maxPosBottomMargin = margin;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update line max element size
|
|
nsSize& mes = aState.lineMaxElementSize;
|
|
if (nsnull != aKidMaxElementSize) {
|
|
if (aKidMaxElementSize->width > mes.width) {
|
|
mes.width = aKidMaxElementSize->width;
|
|
}
|
|
if (aKidMaxElementSize->height > mes.height) {
|
|
mes.height = aKidMaxElementSize->height;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Places and sizes the block-level element, and advances the line.
|
|
// The rect is in the local coordinate space of the kid frame.
|
|
void nsBlockFrame::AddBlockChild(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState,
|
|
nsIFrame* aKidFrame,
|
|
nsRect& aKidRect,
|
|
nsSize* aKidMaxElementSize,
|
|
nsStyleMolecule* aKidMol)
|
|
{
|
|
NS_PRECONDITION(nsnull != aState.lineStart, "bad line");
|
|
|
|
if (NS_STYLE_POSITION_RELATIVE == aKidMol->positionFlags) {
|
|
aState.needRelativePos = PR_TRUE;
|
|
}
|
|
|
|
// Translate from the kid's coordinate space to our coordinate space
|
|
aKidRect.x += aState.borderPadding.left + aKidMol->margin.left;
|
|
aKidRect.y += aState.y + aState.topMargin;
|
|
|
|
// Place and size the child
|
|
aKidFrame->SetRect(aKidRect);
|
|
|
|
aState.AddAscent(aKidRect.height);
|
|
aState.lineLength++;
|
|
|
|
// Is this the widest child frame?
|
|
nscoord xMost = aKidRect.XMost() + aKidMol->margin.right;
|
|
if (xMost > aState.kidXMost) {
|
|
aState.kidXMost = xMost;
|
|
}
|
|
|
|
// Update the max element size
|
|
if (nsnull != aKidMaxElementSize) {
|
|
if (aKidMaxElementSize->width > aState.maxElementSize->width) {
|
|
aState.maxElementSize->width = aKidMaxElementSize->width;
|
|
}
|
|
if (aKidMaxElementSize->height > aState.maxElementSize->height) {
|
|
aState.maxElementSize->height = aKidMaxElementSize->height;
|
|
}
|
|
}
|
|
|
|
// and the bottom line margin information which we'll use when placing
|
|
// the next child
|
|
if (aKidMol->margin.bottom < 0) {
|
|
aState.maxNegBottomMargin = -aKidMol->margin.bottom;
|
|
} else {
|
|
aState.maxPosBottomMargin = aKidMol->margin.bottom;
|
|
}
|
|
|
|
// Update the running y-offset
|
|
aState.y += aKidRect.height + aState.topMargin;
|
|
|
|
// Apply relative positioning if necessary
|
|
nsCSSLayout::RelativePositionChildren(aCX, this, aState.mol, aKidFrame, 1);
|
|
|
|
// Advance to the next line
|
|
aState.AdvanceToNextLine(aKidFrame, aKidRect.height);
|
|
}
|
|
|
|
/**
|
|
* Compute the available size for reflowing the given child at the
|
|
* current x,y position in the state. Note that this may return
|
|
* negative or zero width/height's if we are out of room.
|
|
*/
|
|
void nsBlockFrame::GetAvailSize(nsSize& aResult,
|
|
nsBlockReflowState& aState,
|
|
nsStyleMolecule* aKidMol,
|
|
PRBool aIsInline)
|
|
{
|
|
// Determine the maximum available reflow height for the child
|
|
nscoord yb = aState.borderPadding.top + aState.availSize.height;
|
|
aResult.height = aState.unconstrainedHeight ? NS_UNCONSTRAINEDSIZE :
|
|
yb - aState.y - aState.topMargin;
|
|
|
|
// Determine the maximum available reflow width for the child
|
|
if (aState.unconstrainedWidth) {
|
|
aResult.width = NS_UNCONSTRAINEDSIZE;
|
|
} else if (aIsInline) {
|
|
if (NS_STYLE_DIRECTION_LTR == aState.mol->direction) {
|
|
aResult.width = aState.currentBand->rects[0].XMost() - aState.x;
|
|
} else {
|
|
aResult.width = aState.x - aState.currentBand->rects[0].x;
|
|
}
|
|
} else {
|
|
// It's a block. Don't adjust for the left/right margin here. That happens
|
|
// later on once we know the current left/right edge
|
|
aResult.width = aState.availSize.width;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Push all of the kids that we have not reflowed, starting at
|
|
* aState.lineStart. aPrevKid is the kid previous to aState.lineStart
|
|
* and is also our last child. Note that line length is <B>NOT</B> a
|
|
* reflection of the number of children we are actually pushing
|
|
* (because we don't break the sibling list as we add children to the
|
|
* line).
|
|
*/
|
|
void nsBlockFrame::PushKids(nsBlockReflowState& aState)
|
|
{
|
|
nsIFrame* prevFrame = aState.prevLineLastFrame;
|
|
NS_PRECONDITION(nsnull != prevFrame, "pushing all kids");
|
|
#ifdef NS_DEBUG
|
|
nsIFrame* nextSibling;
|
|
|
|
prevFrame->GetNextSibling(nextSibling);
|
|
NS_PRECONDITION(nextSibling == aState.lineStart, "bad prev line");
|
|
#endif
|
|
|
|
#ifdef NS_DEBUG
|
|
PRInt32 numKids = LengthOf(mFirstChild);
|
|
NS_ASSERTION(numKids == mChildCount, "bad child count");
|
|
#endif
|
|
|
|
#ifdef NOISY
|
|
ListTag(stdout);
|
|
printf(": push kids (childCount=%d)\n", mChildCount);
|
|
DumpFlow();
|
|
#endif
|
|
|
|
PushChildren(aState.lineStart, prevFrame, mLastContentIsComplete);
|
|
SetLastContentOffset(prevFrame);
|
|
|
|
// Set mLastContentIsComplete to the previous lines last content is
|
|
// complete now that the previous line's last child is our last
|
|
// child.
|
|
mLastContentIsComplete = aState.prevLineLastContentIsComplete;
|
|
|
|
// Fix up child count
|
|
// XXX is there a better way? aState.lineLength doesn't work because
|
|
// we might be pushing more than just the pending line.
|
|
nsIFrame* kid = mFirstChild;
|
|
PRInt32 kids = 0;
|
|
while (nsnull != kid) {
|
|
kids++;
|
|
kid->GetNextSibling(kid);
|
|
}
|
|
mChildCount = kids;
|
|
|
|
// Make sure we have no lingering line data
|
|
aState.lineLength = 0;
|
|
aState.lineStart = nsnull;
|
|
|
|
#ifdef NOISY
|
|
ListTag(stdout);
|
|
printf(": push kids done (childCount=%d) [%c]\n", mChildCount,
|
|
(mLastContentIsComplete ? 'T' : 'F'));
|
|
DumpFlow();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Gets a band of available space starting at the specified y-offset. Assumes
|
|
* the local coordinate space is currently set to the upper-left origin of the
|
|
* bounding rect
|
|
*
|
|
* Updates "currentBand" and "x" member data of the block reflow state
|
|
*/
|
|
void nsBlockFrame::GetAvailableSpaceBand(nsBlockReflowState& aState, nscoord aY)
|
|
{
|
|
// Gets a band of available space.
|
|
aState.spaceManager->Translate(aState.borderPadding.left, 0);
|
|
aState.spaceManager->GetBandData(aY, aState.availSize, *aState.currentBand);
|
|
|
|
// XXX This is hack code that needs to change when the space manager interface
|
|
// changes to return both the available and unavailable space
|
|
//
|
|
// We'll get back anywhere between 1 and 3 rects depending on how many floaters
|
|
// there are. Actually the way it currently works we could get back zero rects
|
|
// if there are overlapping left and right floaters occupying all the space
|
|
if (aState.currentBand->count > 1) {
|
|
// If there are three rects then let's assume that there are floaters on the
|
|
// left and right and that only the middle rect is available
|
|
if (aState.currentBand->count == 3) {
|
|
aState.currentBand->rects[0] = aState.currentBand->rects[1];
|
|
} else {
|
|
// There are two rects. That means either a left or right floater. Just use
|
|
// whichever space is larger.
|
|
if (aState.currentBand->rects[1].width > aState.currentBand->rects[0].width) {
|
|
aState.currentBand->rects[0] = aState.currentBand->rects[1];
|
|
}
|
|
}
|
|
}
|
|
aState.spaceManager->Translate(-aState.borderPadding.left, 0);
|
|
aState.x = aState.currentBand->rects[0].x;
|
|
}
|
|
|
|
void nsBlockFrame::ClearFloaters(nsBlockReflowState& aState, PRUint32 aClear)
|
|
{
|
|
// Translate the coordinate space
|
|
aState.spaceManager->Translate(aState.borderPadding.left, 0);
|
|
|
|
getBand:
|
|
nscoord y = aState.y + aState.topMargin;
|
|
PRBool isLeftFloater = PR_FALSE;
|
|
PRBool isRightFloater = PR_FALSE;
|
|
|
|
// Get a band of available space
|
|
aState.spaceManager->GetBandData(y, aState.availSize, *aState.currentBand);
|
|
|
|
if (aState.currentBand->count == 1) {
|
|
if (aState.currentBand->rects[0].width != aState.availSize.width) {
|
|
// Some of the space is taken up by floaters
|
|
if (aState.currentBand->rects[0].x > 0) {
|
|
isLeftFloater = PR_TRUE;
|
|
}
|
|
|
|
if (aState.currentBand->rects[0].XMost() < aState.availSize.width) {
|
|
isRightFloater = PR_TRUE;
|
|
}
|
|
}
|
|
} else if (aState.currentBand->count == 2) {
|
|
if (aState.currentBand->rects[0].width > aState.currentBand->rects[1].width) {
|
|
isRightFloater = PR_TRUE;
|
|
} else {
|
|
isLeftFloater = PR_TRUE;
|
|
|
|
// There may also be a right floater
|
|
if (aState.currentBand->rects[1].XMost() < aState.availSize.width) {
|
|
isRightFloater = PR_TRUE;
|
|
}
|
|
}
|
|
} else {
|
|
// Must be both left and right floaters
|
|
isLeftFloater = PR_TRUE;
|
|
isRightFloater = PR_TRUE;
|
|
}
|
|
|
|
if (isLeftFloater) {
|
|
if ((aClear == NS_STYLE_CLEAR_LEFT) || (aClear == NS_STYLE_CLEAR_BOTH)) {
|
|
aState.y += aState.currentBand->rects[0].height;
|
|
goto getBand;
|
|
}
|
|
}
|
|
if (isRightFloater) {
|
|
if ((aClear == NS_STYLE_CLEAR_RIGHT) || (aClear == NS_STYLE_CLEAR_BOTH)) {
|
|
aState.y += aState.currentBand->rects[0].height;
|
|
goto getBand;
|
|
}
|
|
}
|
|
|
|
aState.spaceManager->Translate(-aState.borderPadding.left, 0);
|
|
}
|
|
|
|
// Bit's for PlaceAndReflowChild return value
|
|
#define PLACE_FIT 0x1
|
|
#define PLACE_FLOWED 0x2
|
|
|
|
PRIntn
|
|
nsBlockFrame::PlaceAndReflowChild(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState,
|
|
nsIFrame* aKidFrame,
|
|
nsStyleMolecule* aKidMol)
|
|
{
|
|
nsSize kidMaxElementSize;
|
|
nsSize* pKidMaxElementSize =
|
|
(nsnull != aState.maxElementSize) ? &kidMaxElementSize : nsnull;
|
|
|
|
// Get line start setup if we are at the start of a new line
|
|
if (nsnull == aState.lineStart) {
|
|
NS_ASSERTION(0 == aState.lineLength, "bad line length");
|
|
aState.lineStart = aKidFrame;
|
|
}
|
|
|
|
// Get kid and its style
|
|
// XXX How is this any different than what was passed in to us as aKidMol?
|
|
nsIContent* kid;
|
|
nsIStyleContext* kidSC;
|
|
|
|
aKidFrame->GetContent(kid);
|
|
aKidFrame->GetStyleContext(aCX, kidSC);
|
|
nsStyleMolecule* kidMol = (nsStyleMolecule*)kidSC->GetData(kStyleMoleculeSID);
|
|
NS_RELEASE(kid);
|
|
|
|
// Figure out if kid is a block element or not
|
|
PRBool isInline = PR_TRUE;
|
|
PRIntn display = kidMol->display;
|
|
if (aState.firstChildIsInsideBullet && (mFirstChild == aKidFrame)) {
|
|
// XXX Special hack for properly reflowing bullets that have the
|
|
// inside value for list-style-position.
|
|
display = NS_STYLE_DISPLAY_INLINE;
|
|
}
|
|
if ((NS_STYLE_DISPLAY_BLOCK == display) ||
|
|
(NS_STYLE_DISPLAY_LIST_ITEM == display)) {
|
|
// Block elements always end up on the next line (unless they are
|
|
// already at the start of the line).
|
|
isInline = PR_FALSE;
|
|
if (aState.lineLength > 0) {
|
|
aState.breakAfterChild = PR_TRUE;
|
|
}
|
|
}
|
|
|
|
// Handle forced break first
|
|
if (aState.breakAfterChild) {
|
|
NS_ASSERTION(aState.lineStart != aKidFrame, "bad line");
|
|
|
|
// Get the last child in the current line
|
|
nsIFrame* lastFrame = aState.lineStart;
|
|
PRInt32 lineLen = aState.lineLength - 1;
|
|
while (--lineLen >= 0) {
|
|
lastFrame->GetNextSibling(lastFrame);
|
|
}
|
|
|
|
if (!AdvanceToNextLine(aCX, aState)) {
|
|
// The previous line didn't fit.
|
|
return 0;
|
|
}
|
|
aState.lineStart = aKidFrame;
|
|
|
|
// Get the style for the last child, and see if it wanted to clear floaters.
|
|
// This handles the BR tag, which is the only inline element for which clear
|
|
// applies
|
|
nsIStyleContext* lastChildSC;
|
|
|
|
lastFrame->GetStyleContext(aCX, lastChildSC);
|
|
nsStyleMolecule* lastChildMol = (nsStyleMolecule*)lastChildSC->GetData(kStyleMoleculeSID);
|
|
if (lastChildMol->clear != NS_STYLE_CLEAR_NONE) {
|
|
ClearFloaters(aState, lastChildMol->clear);
|
|
}
|
|
NS_RELEASE(lastChildSC);
|
|
}
|
|
|
|
// Now that we've handled force breaks (and maybe called AdvanceToNextLine()
|
|
// which checks), remember whether it's an inline frame
|
|
aState.isInline = isInline;
|
|
|
|
// If we're at the beginning of a line then compute the top margin that we
|
|
// should use
|
|
if (aState.lineStart == aKidFrame) {
|
|
// Compute the top margin to use for this line
|
|
aState.topMargin = GetTopMarginFor(aCX, aState, aKidFrame, kidMol, aState.isInline);
|
|
|
|
// If it's an inline element then get a band of available space
|
|
//
|
|
// XXX If we have a current band and there's unused space in that band
|
|
// then avoid this call to get a band...
|
|
if (aState.isInline) {
|
|
GetAvailableSpaceBand(aState, aState.y + aState.topMargin);
|
|
}
|
|
}
|
|
|
|
// Compute the available space to reflow the child into and then
|
|
// reflow it into that space.
|
|
nsSize kidAvailSize;
|
|
GetAvailSize(kidAvailSize, aState, kidMol, aState.isInline);
|
|
if ((aState.currentLineNumber > 0) && (kidAvailSize.height <= 0)) {
|
|
// No more room
|
|
return 0;
|
|
}
|
|
|
|
ReflowStatus status;
|
|
|
|
if (aState.isInline) {
|
|
nsReflowMetrics kidSize;
|
|
|
|
// Inline elements are never passed the space manager
|
|
status = ReflowChild(aKidFrame, aCX, kidSize, kidAvailSize,
|
|
pKidMaxElementSize);
|
|
|
|
// For first children, we skip all the fit checks because we must
|
|
// fit at least one child for a parent to figure what to do with us.
|
|
if ((aState.currentLineNumber > 0) || (aState.lineLength > 0)) {
|
|
NS_ASSERTION(nsnull != aState.lineStart, "bad line start");
|
|
|
|
if (aKidFrame == aState.lineStart) {
|
|
// Width always fits when we are at the logical left margin.
|
|
// Just check the height.
|
|
//
|
|
// XXX This height check isn't correct now that we have bands of
|
|
// available space...
|
|
if (kidSize.height > kidAvailSize.height) {
|
|
// It's too tall
|
|
return PLACE_FLOWED;
|
|
}
|
|
} else {
|
|
// Examine state and if the breakBeforeChild is set and we
|
|
// aren't already on the new line, do the forcing now.
|
|
// XXX Why aren't we doing this check BEFORE we resize reflow the child?
|
|
if (aState.breakBeforeChild) {
|
|
aState.breakBeforeChild = PR_FALSE;
|
|
if (aKidFrame != aState.lineStart) {
|
|
if (!AdvanceToNextLine(aCX, aState)) {
|
|
// Flushing out the line failed.
|
|
return PLACE_FLOWED;
|
|
}
|
|
aState.lineStart = aKidFrame;
|
|
|
|
// Get a band of available space
|
|
GetAvailableSpaceBand(aState, aState.y + aState.topMargin);
|
|
|
|
// Reflow child now that it has the line to itself
|
|
GetAvailSize(kidAvailSize, aState, kidMol, PR_TRUE);
|
|
status = ReflowChild(aKidFrame, aCX, kidSize, kidAvailSize,
|
|
pKidMaxElementSize);
|
|
}
|
|
}
|
|
|
|
// When we are not at the logical left margin then we need
|
|
// to check the width first. If we are too wide then advance
|
|
// to the next line and try reflowing again.
|
|
if (kidSize.width > kidAvailSize.width) {
|
|
// Too wide. Try next line
|
|
if (!AdvanceToNextLine(aCX, aState)) {
|
|
// Flushing out the line failed.
|
|
return PLACE_FLOWED;
|
|
}
|
|
aState.lineStart = aKidFrame;
|
|
|
|
// Get a band of available space
|
|
GetAvailableSpaceBand(aState, aState.y + aState.topMargin);
|
|
|
|
// Reflow splittable children
|
|
SplittableType isSplittable;
|
|
|
|
aKidFrame->IsSplittable(isSplittable);
|
|
if (isSplittable != frNotSplittable) {
|
|
// Update size info now that we are on the next line. Then
|
|
// reflow the child into the new available space.
|
|
GetAvailSize(kidAvailSize, aState, kidMol, PR_TRUE);
|
|
status = ReflowChild(aKidFrame, aCX, kidSize, kidAvailSize,
|
|
pKidMaxElementSize);
|
|
|
|
// If we just reflowed our last child then update the
|
|
// mLastContentIsComplete state.
|
|
nsIFrame* nextSibling;
|
|
|
|
aKidFrame->GetNextSibling(nextSibling);
|
|
if (nsnull == nextSibling) {
|
|
// Use state from the reflow we just did
|
|
mLastContentIsComplete = PRBool(status == frComplete);
|
|
}
|
|
}
|
|
|
|
// XXX This height check isn't correct now that we have bands of
|
|
// available space...
|
|
if (kidSize.height > kidAvailSize.height) {
|
|
// It's too tall on the next line
|
|
return PLACE_FLOWED;
|
|
}
|
|
// It's ok if it's too wide on the next line.
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add child to the line
|
|
AddInlineChildToLine(aCX, aState, aKidFrame, kidSize, pKidMaxElementSize, kidMol);
|
|
|
|
} else {
|
|
nsRect kidRect;
|
|
|
|
// Does the block-level element want to clear any floaters that impact
|
|
// it? Note that the clear property only applies to block-level elements
|
|
// and the BR tag
|
|
if (aKidMol->clear != NS_STYLE_CLEAR_NONE) {
|
|
ClearFloaters(aState, aKidMol->clear);
|
|
GetAvailSize(kidAvailSize, aState, kidMol, PR_FALSE);
|
|
if ((aState.currentLineNumber > 0) && (kidAvailSize.height <= 0)) {
|
|
// No more room
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Give the block its own local coordinate space.. Note: ReflowChild()
|
|
// will adjust for the child's left/right margin after determining the
|
|
// current left/right edge
|
|
aState.spaceManager->Translate(aState.borderPadding.left, 0);
|
|
// Give the block-level element the opportunity to use the space manager
|
|
status = ReflowChild(aKidFrame, aCX, kidMol, aState.spaceManager, kidAvailSize,
|
|
kidRect, pKidMaxElementSize);
|
|
aState.spaceManager->Translate(-aState.borderPadding.left, 0);
|
|
|
|
// For first children, we skip all the fit checks because we must
|
|
// fit at least one child for a parent to figure what to do with us.
|
|
if ((aState.currentLineNumber > 0) || (aState.lineLength > 0)) {
|
|
// Block elements always fit horizontally (because they are
|
|
// always placed at the logical left margin). Check to see if
|
|
// the block fits vertically
|
|
if (kidRect.YMost() > kidAvailSize.height) {
|
|
// Nope
|
|
return PLACE_FLOWED;
|
|
}
|
|
}
|
|
|
|
// Add block child
|
|
// XXX We need to set lastContentIsComplete here, because AddBlockChild()
|
|
// calls AdvaneceToNextLine(). We need to restructure the flow of control,
|
|
// and use a state machine...
|
|
aState.lastContentIsComplete = PRBool(status == frComplete);
|
|
AddBlockChild(aCX, aState, aKidFrame, kidRect, pKidMaxElementSize, kidMol);
|
|
}
|
|
|
|
// If we just reflowed our last child then update the
|
|
// mLastContentIsComplete state.
|
|
nsIFrame* nextSibling;
|
|
|
|
aKidFrame->GetNextSibling(nextSibling);
|
|
if (nsnull == nextSibling) {
|
|
// Use state from the reflow we just did
|
|
mLastContentIsComplete = PRBool(status == frComplete);
|
|
}
|
|
|
|
aState.lastContentIsComplete = PRBool(status == frComplete);
|
|
if (aState.isInline && (frNotComplete == status)) {
|
|
// Since the inline child didn't complete its reflow we *know*
|
|
// that a continuation of it can't possibly fit on the current
|
|
// line. Therefore, set a flag in the state that will cause the
|
|
// a line break before the next frame is placed.
|
|
aState.breakAfterChild = PR_TRUE;
|
|
}
|
|
NS_RELEASE(kidSC);
|
|
|
|
aState.reflowStatus = status;
|
|
return PLACE_FLOWED | PLACE_FIT;
|
|
}
|
|
|
|
/**
|
|
* Reflow the existing frames.
|
|
*
|
|
* @param aCX presentation context to use
|
|
* @param aState <b>in out</b> parameter which tracks the state of
|
|
* reflow for the block frame.
|
|
* @return true if we successfully reflowed all the mapped children and false
|
|
* otherwise, e.g. we pushed children to the next in flow
|
|
*/
|
|
PRBool
|
|
nsBlockFrame::ReflowMappedChildren(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState)
|
|
{
|
|
#ifdef NS_DEBUG
|
|
VerifyLastIsComplete();
|
|
#endif
|
|
#ifdef NOISY
|
|
ListTag(stdout);
|
|
printf(": reflow mapped (childCount=%d) [%d,%d,%c]\n",
|
|
mChildCount,
|
|
mFirstContentOffset, mLastContentOffset,
|
|
(mLastContentIsComplete ? 'T' : 'F'));
|
|
DumpFlow();
|
|
#endif
|
|
|
|
PRBool result = PR_TRUE;
|
|
nsIFrame* kidFrame;
|
|
nsIFrame* prevKidFrame = nsnull;
|
|
|
|
for (kidFrame = mFirstChild; nsnull != kidFrame; ) {
|
|
nsIContent* kid;
|
|
|
|
kidFrame->GetContent(kid);
|
|
nsIStyleContext* kidSC = aCX->ResolveStyleContextFor(kid, this);
|
|
nsStyleMolecule* kidMol = (nsStyleMolecule*)kidSC->GetData(kStyleMoleculeSID);
|
|
NS_RELEASE(kid);
|
|
|
|
// Attempt to place and reflow the child
|
|
|
|
// XXX if child is not splittable and it fits just place it where
|
|
// it is, otherwise advance to the next line and place it there if
|
|
// possible
|
|
|
|
PRIntn placeStatus = PlaceAndReflowChild(aCX, aState, kidFrame, kidMol);
|
|
ReflowStatus status = aState.reflowStatus;
|
|
NS_RELEASE(kidSC);
|
|
if (0 == (placeStatus & PLACE_FIT)) {
|
|
// The child doesn't fit. Push it and any remaining children.
|
|
PushKids(aState);
|
|
result = PR_FALSE;
|
|
goto push_done;
|
|
}
|
|
|
|
// Is the child complete?
|
|
nsIFrame* kidNextInFlow;
|
|
|
|
kidFrame->GetNextInFlow(kidNextInFlow);
|
|
if (frComplete == status) {
|
|
// Yes, the child is complete
|
|
NS_ASSERTION(nsnull == kidNextInFlow, "bad child flow list");
|
|
} else {
|
|
// No the child isn't complete
|
|
if (nsnull == kidNextInFlow) {
|
|
// The child doesn't have a next-in-flow so create a continuing
|
|
// frame. This hooks the child into the flow
|
|
nsIFrame* continuingFrame;
|
|
|
|
kidFrame->CreateContinuingFrame(aCX, this, continuingFrame);
|
|
NS_ASSERTION(nsnull != continuingFrame, "frame creation failed");
|
|
|
|
// Add the continuing frame to the sibling list
|
|
nsIFrame* nextSib;
|
|
|
|
kidFrame->GetNextSibling(nextSib);
|
|
continuingFrame->SetNextSibling(nextSib);
|
|
kidFrame->SetNextSibling(continuingFrame);
|
|
mChildCount++;
|
|
}
|
|
|
|
// Unlike the inline frame code we can't assume that we used
|
|
// up all of our space because the child's reflow status is
|
|
// frNotComplete. Instead, the child is probably split and
|
|
// we need to reflow the continuations as well.
|
|
}
|
|
|
|
// Get the next child frame
|
|
prevKidFrame = kidFrame;
|
|
kidFrame->GetNextSibling(kidFrame);
|
|
}
|
|
|
|
push_done:
|
|
#ifdef NS_DEBUG
|
|
nsIFrame* lastChild;
|
|
PRInt32 lastIndexInParent;
|
|
|
|
LastChild(lastChild);
|
|
lastChild->GetIndexInParent(lastIndexInParent);
|
|
NS_POSTCONDITION(lastIndexInParent == mLastContentOffset, "bad last content offset");
|
|
|
|
PRInt32 len = LengthOf(mFirstChild);
|
|
NS_POSTCONDITION(len == mChildCount, "bad child count");
|
|
VerifyLastIsComplete();
|
|
#endif
|
|
|
|
#ifdef NOISY
|
|
ListTag(stdout);
|
|
printf(": reflow mapped %sok (childCount=%d) [%d,%d,%c]\n",
|
|
(result ? "" : "NOT "),
|
|
mChildCount,
|
|
mFirstContentOffset, mLastContentOffset,
|
|
(mLastContentIsComplete ? 'T' : 'F'));
|
|
DumpFlow();
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
PRBool nsBlockFrame::MoreToReflow(nsIPresContext* aCX)
|
|
{
|
|
PRBool rv = PR_FALSE;
|
|
if (IsPseudoFrame()) {
|
|
// Get the next content object that we would like to reflow
|
|
PRInt32 kidIndex = NextChildOffset();
|
|
nsIContent* kid = mContent->ChildAt(kidIndex);
|
|
if (nsnull != kid) {
|
|
// Resolve style for the kid
|
|
nsIStyleContext* kidSC = aCX->ResolveStyleContextFor(kid, this);
|
|
nsStyleMolecule* kidMol = (nsStyleMolecule*)kidSC->GetData(kStyleMoleculeSID);
|
|
switch (kidMol->display) {
|
|
case NS_STYLE_DISPLAY_BLOCK:
|
|
case NS_STYLE_DISPLAY_LIST_ITEM:
|
|
// Block pseudo-frames do not contain other block elements
|
|
break;
|
|
|
|
default:
|
|
rv = PR_TRUE;
|
|
break;
|
|
}
|
|
NS_RELEASE(kidSC);
|
|
NS_RELEASE(kid);
|
|
}
|
|
} else {
|
|
if (NextChildOffset() < mContent->ChildCount()) {
|
|
rv = PR_TRUE;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Create new frames for content we haven't yet mapped
|
|
*
|
|
* @param aCX presentation context to use
|
|
* @return frComplete if all content has been mapped and frNotComplete
|
|
* if we should be continued
|
|
*/
|
|
nsIFrame::ReflowStatus
|
|
nsBlockFrame::ReflowAppendedChildren(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState)
|
|
{
|
|
#ifdef NS_DEBUG
|
|
VerifyLastIsComplete();
|
|
#endif
|
|
nsIFrame* kidPrevInFlow = nsnull;
|
|
ReflowStatus result = frNotComplete;
|
|
|
|
// If we have no children and we have a prev-in-flow then we need to pick
|
|
// up where it left off. If we have children, e.g. we're being resized, then
|
|
// our content offset should already be set correctly...
|
|
if ((nsnull == mFirstChild) && (nsnull != mPrevInFlow)) {
|
|
nsBlockFrame* prev = (nsBlockFrame*) mPrevInFlow;
|
|
NS_ASSERTION(prev->mLastContentOffset >= prev->mFirstContentOffset, "bad prevInFlow");
|
|
mFirstContentOffset = prev->NextChildOffset();
|
|
if (PR_FALSE == prev->mLastContentIsComplete) {
|
|
// Our prev-in-flow's last child is not complete
|
|
prev->LastChild(kidPrevInFlow);
|
|
}
|
|
}
|
|
|
|
// Place our children, one at a time until we are out of children
|
|
PRInt32 kidIndex = NextChildOffset();
|
|
nsIFrame* kidFrame = nsnull;
|
|
nsIFrame* prevKidFrame;
|
|
|
|
LastChild(prevKidFrame);
|
|
for (;;) {
|
|
// Get the next content object
|
|
nsIContent* kid = mContent->ChildAt(kidIndex);
|
|
if (nsnull == kid) {
|
|
result = frComplete;
|
|
break;
|
|
}
|
|
|
|
// Resolve style for the kid
|
|
nsIStyleContext* kidSC = aCX->ResolveStyleContextFor(kid, this);
|
|
nsStyleMolecule* kidMol = (nsStyleMolecule*)kidSC->GetData(kStyleMoleculeSID);
|
|
|
|
// Is it a floater?
|
|
if (kidMol->floats != NS_STYLE_FLOAT_NONE) {
|
|
PlaceholderFrame::NewFrame(&kidFrame, kid, kidIndex, this);
|
|
kidFrame->SetStyleContext(kidSC);
|
|
} else if (nsnull == kidPrevInFlow) {
|
|
// Create initial frame for the child
|
|
nsIContentDelegate* kidDel;
|
|
nsresult fr;
|
|
switch (kidMol->display) {
|
|
case NS_STYLE_DISPLAY_BLOCK:
|
|
case NS_STYLE_DISPLAY_LIST_ITEM:
|
|
// Pseudo block frames do not contain other block elements
|
|
// unless the block element would be the first child.
|
|
if (IsPseudoFrame()) {
|
|
// If we're being used as a pseudo frame, i.e. we map the same
|
|
// content as our parent then we want to indicate we're complete;
|
|
// otherwise we'll be continued and go on mapping children...
|
|
|
|
// It better be true that we are not being asked to flow a
|
|
// block element as our first child. That means the body
|
|
// decided it needed a pseudo-frame when it shouldn't have.
|
|
NS_ASSERTION(nsnull != mFirstChild, "bad body");
|
|
|
|
NS_RELEASE(kidSC);
|
|
NS_RELEASE(kid);
|
|
result = frComplete;
|
|
goto done;
|
|
}
|
|
// FALL THROUGH (and create frame)
|
|
|
|
case NS_STYLE_DISPLAY_INLINE:
|
|
kidDel = kid->GetDelegate(aCX);
|
|
kidFrame = kidDel->CreateFrame(aCX, kid, kidIndex, this);
|
|
NS_RELEASE(kidDel);
|
|
break;
|
|
|
|
default:
|
|
NS_ASSERTION(nsnull == kidPrevInFlow, "bad prev in flow");
|
|
fr = nsFrame::NewFrame(&kidFrame, kid, kidIndex, this);
|
|
break;
|
|
}
|
|
kidFrame->SetStyleContext(kidSC);
|
|
} else {
|
|
// Since kid has a prev-in-flow, use that to create the next
|
|
// frame.
|
|
kidPrevInFlow->CreateContinuingFrame(aCX, this, kidFrame);
|
|
}
|
|
|
|
// Link child frame into the list of children. If the frame ends
|
|
// up not fitting and getting pushed, the PushKids code will fixup
|
|
// the child count for us.
|
|
if (nsnull != prevKidFrame) {
|
|
#ifdef NS_DEBUG
|
|
nsIFrame* nextSibling;
|
|
|
|
prevKidFrame->GetNextSibling(nextSibling);
|
|
NS_ASSERTION(nsnull == nextSibling, "bad append");
|
|
#endif
|
|
prevKidFrame->SetNextSibling(kidFrame);
|
|
} else {
|
|
NS_ASSERTION(nsnull == mFirstChild, "bad create");
|
|
mFirstChild = kidFrame;
|
|
SetFirstContentOffset(kidFrame);
|
|
}
|
|
prevKidFrame = kidFrame;
|
|
mChildCount++;
|
|
|
|
// Reflow child frame as many times as necessary until it is
|
|
// complete.
|
|
ReflowStatus status;
|
|
do {
|
|
PRIntn placeStatus = PlaceAndReflowChild(aCX, aState, kidFrame, kidMol);
|
|
status = aState.reflowStatus;
|
|
if (0 == (placeStatus & PLACE_FIT)) {
|
|
// We ran out of room.
|
|
nsIFrame* kidNextInFlow;
|
|
|
|
kidFrame->GetNextInFlow(kidNextInFlow);
|
|
mLastContentIsComplete = PRBool(nsnull == kidNextInFlow);
|
|
PushKids(aState);
|
|
|
|
NS_RELEASE(kid);
|
|
NS_RELEASE(kidSC);
|
|
goto push_done;
|
|
}
|
|
|
|
// Did the child complete?
|
|
prevKidFrame = kidFrame;
|
|
if (frNotComplete == status) {
|
|
// Child didn't complete so create a continuing frame
|
|
kidPrevInFlow = kidFrame;
|
|
nsIFrame* continuingFrame;
|
|
|
|
kidFrame->CreateContinuingFrame(aCX, this, continuingFrame);
|
|
|
|
// Add the continuing frame to the sibling list
|
|
nsIFrame* kidNextSibling;
|
|
|
|
kidFrame->GetNextSibling(kidNextSibling);
|
|
continuingFrame->SetNextSibling(kidNextSibling);
|
|
kidFrame->SetNextSibling(continuingFrame);
|
|
kidFrame = continuingFrame;
|
|
mChildCount++;
|
|
|
|
// Switch to new kid style
|
|
NS_RELEASE(kidSC);
|
|
kidFrame->GetStyleContext(aCX, kidSC);
|
|
kidMol = (nsStyleMolecule*)kidSC->GetData(kStyleMoleculeSID);
|
|
}
|
|
#ifdef NS_DEBUG
|
|
nsIFrame* kidNextInFlow;
|
|
|
|
kidFrame->GetNextInFlow(kidNextInFlow);
|
|
NS_ASSERTION(nsnull == kidNextInFlow, "huh?");
|
|
#endif
|
|
} while (frNotComplete == status);
|
|
NS_RELEASE(kid);
|
|
NS_RELEASE(kidSC);
|
|
|
|
// The child that we just reflowed is complete
|
|
#ifdef NS_DEBUG
|
|
nsIFrame* kidNextInFlow;
|
|
|
|
kidFrame->GetNextInFlow(kidNextInFlow);
|
|
NS_ASSERTION(nsnull == kidNextInFlow, "bad child flow list");
|
|
#endif
|
|
kidIndex++;
|
|
kidPrevInFlow = nsnull;
|
|
}
|
|
|
|
done:
|
|
// To get here we either completely reflowed all our appended
|
|
// children OR we are a pseudo-frame and we ran into a block
|
|
// element. In either case our last content MUST be complete.
|
|
NS_ASSERTION(PR_TRUE == aState.lastContentIsComplete, "bad state");
|
|
NS_ASSERTION(IsLastChild(prevKidFrame), "bad last child");
|
|
SetLastContentOffset(prevKidFrame);
|
|
|
|
push_done:
|
|
#ifdef NS_DEBUG
|
|
PRInt32 len = LengthOf(mFirstChild);
|
|
NS_ASSERTION(len == mChildCount, "bad child count");
|
|
VerifyLastIsComplete();
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Pullup frames from our next in flow and try to place them. Before
|
|
* this is called our previously mapped children, if any have been
|
|
* reflowed which means that the block reflow state's x and y
|
|
* coordinates and other data are ready to go.
|
|
*
|
|
* Return true if we pulled everything up.
|
|
*/
|
|
PRBool
|
|
nsBlockFrame::PullUpChildren(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState)
|
|
{
|
|
#ifdef NS_DEBUG
|
|
VerifyLastIsComplete();
|
|
#endif
|
|
#ifdef NOISY
|
|
ListTag(stdout);
|
|
printf(": pullup (childCount=%d) [%d,%d,%c]\n",
|
|
mChildCount,
|
|
mFirstContentOffset, mLastContentOffset,
|
|
(mLastContentIsComplete ? 'T' : 'F'));
|
|
DumpFlow();
|
|
#endif
|
|
|
|
PRBool result = PR_TRUE;
|
|
nsBlockFrame* nextInFlow = (nsBlockFrame*) mNextInFlow;
|
|
nsIFrame* prevKidFrame;
|
|
|
|
LastChild(prevKidFrame);
|
|
while (nsnull != nextInFlow) {
|
|
// Get first available frame from the next-in-flow
|
|
nsIFrame* kidFrame = PullUpOneChild(nextInFlow, prevKidFrame);
|
|
if (nsnull == kidFrame) {
|
|
// We've pulled up all the children from that next-in-flow, so
|
|
// move to the next next-in-flow.
|
|
nextInFlow = (nsBlockFrame*) nextInFlow->mNextInFlow;
|
|
continue;
|
|
}
|
|
|
|
// Get style information for the pulled up kid
|
|
nsIContent* kid;
|
|
|
|
kidFrame->GetContent(kid);
|
|
nsIStyleContext* kidSC = aCX->ResolveStyleContextFor(kid, this);
|
|
nsStyleMolecule* kidMol = (nsStyleMolecule*)kidSC->GetData(kStyleMoleculeSID);
|
|
|
|
ReflowStatus status;
|
|
do {
|
|
PRIntn placeStatus = PlaceAndReflowChild(aCX, aState, kidFrame, kidMol);
|
|
status = aState.reflowStatus;
|
|
if (0 == (placeStatus & PLACE_FIT)) {
|
|
// Push the kids that didn't fit back down to the next-in-flow
|
|
nsIFrame* kidNextInFlow;
|
|
|
|
kidFrame->GetNextInFlow(kidNextInFlow);
|
|
mLastContentIsComplete = PRBool(nsnull == kidNextInFlow);
|
|
PushKids(aState);
|
|
|
|
result = PR_FALSE;
|
|
NS_RELEASE(kid);
|
|
NS_RELEASE(kidSC);
|
|
goto push_done;
|
|
}
|
|
|
|
if (frNotComplete == status) {
|
|
// Child is not complete
|
|
nsIFrame* kidNextInFlow;
|
|
|
|
kidFrame->GetNextInFlow(kidNextInFlow);
|
|
if (nsnull == kidNextInFlow) {
|
|
// Create a continuing frame for the incomplete child
|
|
nsIFrame* continuingFrame;
|
|
|
|
kidFrame->CreateContinuingFrame(aCX, this, continuingFrame);
|
|
|
|
// Add the continuing frame to our sibling list.
|
|
nsIFrame* nextSibling;
|
|
|
|
kidFrame->GetNextSibling(nextSibling);
|
|
continuingFrame->SetNextSibling(nextSibling);
|
|
kidFrame->SetNextSibling(continuingFrame);
|
|
prevKidFrame = kidFrame;
|
|
kidFrame = continuingFrame;
|
|
mChildCount++;
|
|
|
|
// Switch to new kid style
|
|
NS_RELEASE(kidSC);
|
|
kidFrame->GetStyleContext(aCX, kidSC);
|
|
kidMol = (nsStyleMolecule*)kidSC->GetData(kStyleMoleculeSID);
|
|
} else {
|
|
// The child has a next-in-flow, but it's not one of ours.
|
|
// It *must* be in one of our next-in-flows. Collect it
|
|
// then.
|
|
NS_ASSERTION(!IsChild(kidNextInFlow), "busted kid next-in-flow");
|
|
break;
|
|
}
|
|
}
|
|
} while (frNotComplete == status);
|
|
NS_RELEASE(kid);
|
|
NS_RELEASE(kidSC);
|
|
|
|
prevKidFrame = kidFrame;
|
|
}
|
|
|
|
if (nsnull != prevKidFrame) {
|
|
// The only way we can get here is by pulling up every last child
|
|
// in our next-in-flows (and reflowing any continunations they
|
|
// have). Therefore we KNOW that our last child is complete.
|
|
NS_ASSERTION(PR_TRUE == aState.lastContentIsComplete, "bad state");
|
|
NS_ASSERTION(IsLastChild(prevKidFrame), "bad last child");
|
|
SetLastContentOffset(prevKidFrame);
|
|
}
|
|
|
|
push_done:;
|
|
|
|
if (result == PR_FALSE) {
|
|
// If our next-in-flow is empty OR our next next-in-flow is empty
|
|
// then adjust the offsets of all of the empty next-in-flows.
|
|
nextInFlow = (nsBlockFrame*) mNextInFlow;
|
|
if ((0 == nextInFlow->mChildCount) ||
|
|
((nsnull != nextInFlow->mNextInFlow) &&
|
|
(0 == ((nsBlockFrame*)(nextInFlow->mNextInFlow))->mChildCount))) {
|
|
// We didn't pullup everything and we need to fixup one of our
|
|
// next-in-flows content offsets.
|
|
AdjustOffsetOfEmptyNextInFlows();
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef NS_DEBUG
|
|
PRInt32 len = LengthOf(mFirstChild);
|
|
NS_ASSERTION(len == mChildCount, "bad child count");
|
|
VerifyLastIsComplete();
|
|
#endif
|
|
#ifdef NOISY
|
|
ListTag(stdout);
|
|
printf(": pullup %sok (childCount=%d) [%d,%d,%c]\n",
|
|
(result ? "" : "NOT "),
|
|
mChildCount,
|
|
mFirstContentOffset, mLastContentOffset,
|
|
(mLastContentIsComplete ? 'T' : 'F'));
|
|
DumpFlow();
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
void nsBlockFrame::SetupState(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState,
|
|
const nsSize& aMaxSize,
|
|
nsSize* aMaxElementSize,
|
|
nsISpaceManager* aSpaceManager)
|
|
{
|
|
// Setup reflow state
|
|
nsStyleMolecule* mol =
|
|
(nsStyleMolecule*)mStyleContext->GetData(kStyleMoleculeSID);
|
|
nsStyleFont* font =
|
|
(nsStyleFont*)mStyleContext->GetData(kStyleFontSID);
|
|
aState.Init(aMaxSize, aMaxElementSize, font, mol, aSpaceManager);
|
|
|
|
// Apply border and padding adjustments for regular frames only
|
|
if (PR_FALSE == IsPseudoFrame()) {
|
|
aState.borderPadding = mol->borderPadding;
|
|
aState.y = mol->borderPadding.top;
|
|
aState.availSize.width -=
|
|
(mol->borderPadding.left + mol->borderPadding.right);
|
|
aState.availSize.height -=
|
|
(mol->borderPadding.top + mol->borderPadding.bottom);
|
|
} else {
|
|
aState.borderPadding.SizeTo(0, 0, 0, 0);
|
|
}
|
|
|
|
// Setup initial list ordinal value
|
|
nsIAtom* tag = mContent->GetTag();
|
|
if ((tag == nsHTMLAtoms::ul) || (tag == nsHTMLAtoms::ol) ||
|
|
(tag == nsHTMLAtoms::menu) || (tag == nsHTMLAtoms::dir)) {
|
|
nsHTMLValue value;
|
|
if (eContentAttr_HasValue ==
|
|
((nsIHTMLContent*)mContent)->GetAttribute(nsHTMLAtoms::start, value)) {
|
|
if (eHTMLUnit_Absolute == value.GetUnit()) {
|
|
aState.nextListOrdinal = value.GetIntValue();
|
|
}
|
|
}
|
|
}
|
|
NS_RELEASE(tag);
|
|
|
|
mCurrentState = &aState;
|
|
}
|
|
|
|
#include "nsUnitConversion.h"/* XXX */
|
|
NS_METHOD nsBlockFrame::ResizeReflow(nsIPresContext* aCX,
|
|
nsISpaceManager* aSpaceManager,
|
|
const nsSize& aMaxSize,
|
|
nsRect& aDesiredRect,
|
|
nsSize* aMaxElementSize,
|
|
ReflowStatus& aStatus)
|
|
{
|
|
nsBlockReflowState state;
|
|
SetupState(aCX, state, aMaxSize, aMaxElementSize, aSpaceManager);
|
|
return DoResizeReflow(aCX, state, aDesiredRect, aStatus);
|
|
}
|
|
|
|
nsresult nsBlockFrame::DoResizeReflow(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState,
|
|
nsRect& aDesiredRect,
|
|
ReflowStatus& aStatus)
|
|
{
|
|
#ifdef NS_DEBUG
|
|
PreReflowCheck();
|
|
#endif
|
|
#ifdef NOISY
|
|
ListTag(stdout);
|
|
printf(": resize reflow %g,%g\n",
|
|
NS_TWIPS_TO_POINTS_FLOAT(aState.availSize.width),
|
|
NS_TWIPS_TO_POINTS_FLOAT(aState.availSize.height));
|
|
DumpFlow();
|
|
#endif
|
|
|
|
// Zap old line data
|
|
if (nsnull != mLines) {
|
|
delete mLines;
|
|
mLines = nsnull;
|
|
}
|
|
mNumLines = 0;
|
|
|
|
// Check for an overflow list
|
|
MoveOverflowToChildList();
|
|
|
|
// Before we start reflowing, cache a pointer to our state structure
|
|
// so that inline frames can find it.
|
|
nsIPresShell* shell = aCX->GetShell();
|
|
shell->PutCachedData(this, &aState);
|
|
|
|
// First reflow any existing frames
|
|
PRBool reflowMappedOK = PR_TRUE;
|
|
aStatus = frComplete;
|
|
if (nsnull != mFirstChild) {
|
|
reflowMappedOK = ReflowMappedChildren(aCX, aState);
|
|
if (!reflowMappedOK) {
|
|
aStatus = frNotComplete;
|
|
}
|
|
}
|
|
|
|
if (reflowMappedOK) {
|
|
// Any space left?
|
|
nscoord yb = aState.borderPadding.top + aState.availSize.height;
|
|
if ((nsnull != mFirstChild) && (aState.y >= yb)) {
|
|
// No space left. Don't try to pull-up children or reflow
|
|
// unmapped. We need to return the correct completion status,
|
|
// so see if there is more to reflow.
|
|
if (MoreToReflow(aCX)) {
|
|
aStatus = frNotComplete;
|
|
}
|
|
} else if (MoreToReflow(aCX)) {
|
|
// Try and pull-up some children from a next-in-flow
|
|
if ((nsnull == mNextInFlow) || PullUpChildren(aCX, aState)) {
|
|
// If we still have unmapped children then create some new frames
|
|
if (MoreToReflow(aCX)) {
|
|
aStatus = ReflowAppendedChildren(aCX, aState);
|
|
}
|
|
} else {
|
|
// We were unable to pull-up all the existing frames from the
|
|
// next in flow
|
|
aStatus = frNotComplete;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deal with last line - make sure it gets vertically and
|
|
// horizontally aligned. This also updates the state's y coordinate
|
|
// which is good because that's how we size ourselves.
|
|
if (0 != aState.lineLength) {
|
|
if (!AdvanceToNextLine(aCX, aState)) {
|
|
// The last line of output doesn't fit. Push all of the kids to
|
|
// the next in flow and change our reflow status to not complete
|
|
// so that we are continued.
|
|
#ifdef NOISY
|
|
ListTag(stdout);
|
|
printf(": pushing kids since last line doesn't fit\n");
|
|
#endif
|
|
|
|
PushKids(aState);
|
|
aStatus = frNotComplete;
|
|
}
|
|
}
|
|
|
|
if (frComplete == aStatus) {
|
|
// Don't forget to add in the bottom margin from our last child.
|
|
// Only add it in if there is room for it.
|
|
nscoord margin = aState.prevMaxPosBottomMargin -
|
|
aState.prevMaxNegBottomMargin;
|
|
nscoord y = aState.y + margin;
|
|
if (y <= aState.borderPadding.top + aState.availSize.height) {
|
|
aState.y = y;
|
|
}
|
|
}
|
|
|
|
// Now that reflow has finished, remove the cached pointer
|
|
shell->RemoveCachedData(this);
|
|
NS_RELEASE(shell);
|
|
|
|
// Translate state.lineLengths into an integer array
|
|
mNumLines = aState.lineLengths.Count();
|
|
if (mNumLines > 0) {
|
|
mLines = new PRInt32[mNumLines];
|
|
for (PRInt32 i = 0; i < mNumLines; i++) {
|
|
PRInt32 ll = (PRInt32) aState.lineLengths.ElementAt(i);
|
|
mLines[i] = ll;
|
|
}
|
|
}
|
|
|
|
if (!aState.unconstrainedWidth && aState.justifying) {
|
|
// Perform justification now that we know how many lines we have.
|
|
JustifyLines(aCX, aState);
|
|
}
|
|
|
|
// Return our desired rect and our status
|
|
aDesiredRect.x = 0;
|
|
aDesiredRect.y = 0;
|
|
aDesiredRect.width = aState.kidXMost + aState.borderPadding.right;
|
|
if (!aState.unconstrainedWidth) {
|
|
// Make sure we're at least as wide as the max size we were given
|
|
nscoord maxWidth = aState.availSize.width + aState.borderPadding.left +
|
|
aState.borderPadding.right;
|
|
|
|
if (aDesiredRect.width < maxWidth) {
|
|
aDesiredRect.width = maxWidth;
|
|
}
|
|
}
|
|
aState.y += aState.borderPadding.bottom;
|
|
aDesiredRect.height = aState.y;
|
|
|
|
#ifdef NS_DEBUG
|
|
PostReflowCheck(aStatus);
|
|
#endif
|
|
#ifdef NOISY
|
|
ListTag(stdout);
|
|
printf(": resize reflow %g,%g %scomplete [%d,%d,%c]\n",
|
|
NS_TWIPS_TO_POINTS_FLOAT(aState.availSize.width),
|
|
NS_TWIPS_TO_POINTS_FLOAT(aState.availSize.height),
|
|
((status == frNotComplete) ? "NOT " : ""),
|
|
mFirstContentOffset, mLastContentOffset,
|
|
(mLastContentIsComplete ? 'T' : 'F')
|
|
);
|
|
DumpFlow();
|
|
#endif
|
|
mCurrentState = nsnull;
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsBlockFrame::JustifyLines(nsIPresContext* aCX,
|
|
nsBlockReflowState& aState)
|
|
{
|
|
// XXX we don't justify the last line; what if we are continued,
|
|
// should we do it then?
|
|
nsIFrame* kid = mFirstChild;
|
|
for (PRInt32 i = 0; i < mNumLines; i++) {
|
|
nsIFrame* lineStart = kid;
|
|
PRInt32 lineLength = mLines[i];
|
|
if (i < mNumLines - 1) {
|
|
if (1 == lineLength) {
|
|
// For lines with one element on them we can take a shortcut and
|
|
// let them do the justification. Note that we still call
|
|
// JustifyReflow even if the available space is zero in case the
|
|
// child is a hunk of text that ends in whitespace. The whitespace
|
|
// will be distributed elsewhere causing a proper flush right look
|
|
// for the last word.
|
|
nsRect r;
|
|
kid->GetRect(r);
|
|
nscoord maxWidth = aState.availSize.width;
|
|
nscoord availableSpace = maxWidth - r.width;
|
|
nscoord maxAvailSpace = nscoord(maxWidth * 0.1f);
|
|
if ((availableSpace >= 0) && (availableSpace < maxAvailSpace)) {
|
|
kid->JustifyReflow(aCX, availableSpace);
|
|
kid->SizeTo(r.width + availableSpace, r.height);
|
|
}
|
|
kid->GetNextSibling(kid);
|
|
} else {
|
|
// XXX Get justification of multiple elements working
|
|
while (--lineLength >= 0) {
|
|
kid->GetNextSibling(kid);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally, now that the in-flow positions of the line's frames are
|
|
// known we can apply relative positioning if any of them need it.
|
|
nsCSSLayout::RelativePositionChildren(aCX, this, aState.mol,
|
|
lineStart, mLines[i]);
|
|
}
|
|
}
|
|
|
|
NS_METHOD nsBlockFrame::IsSplittable(SplittableType& aIsSplittable) const
|
|
{
|
|
aIsSplittable = frSplittableNonRectangular;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_METHOD nsBlockFrame::CreateContinuingFrame(nsIPresContext* aCX,
|
|
nsIFrame* aParent,
|
|
nsIFrame*& aContinuingFrame)
|
|
{
|
|
nsBlockFrame* cf = new nsBlockFrame(mContent, mIndexInParent, aParent);
|
|
PrepareContinuingFrame(aCX, aParent, cf);
|
|
aContinuingFrame = cf;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_METHOD nsBlockFrame::IncrementalReflow(nsIPresContext* aCX,
|
|
nsISpaceManager* aSpaceManager,
|
|
const nsSize& aMaxSize,
|
|
nsRect& aDesiredRect,
|
|
nsReflowCommand& aReflowCommand,
|
|
ReflowStatus& aStatus)
|
|
{
|
|
aStatus = frComplete;
|
|
|
|
if (aReflowCommand.GetTarget() == this) {
|
|
// XXX for now, just do a complete reflow mapped (it'll kinda
|
|
// work, but it's slow)
|
|
|
|
nsBlockReflowState state;
|
|
SetupState(aCX, state, aMaxSize, nsnull, aSpaceManager);
|
|
PRBool reflowMappedOK = ReflowMappedChildren(aCX, state);
|
|
if (!reflowMappedOK) {
|
|
aStatus = frNotComplete;
|
|
}
|
|
} else {
|
|
// XXX not yet implemented
|
|
NS_ABORT();
|
|
// XXX work to compute initial state goes *HERE*
|
|
aDesiredRect.x = 0;
|
|
aDesiredRect.y = 0;
|
|
aDesiredRect.width = 0;
|
|
aDesiredRect.height = 0;
|
|
}
|
|
|
|
mCurrentState = nsnull;
|
|
return NS_OK;
|
|
}
|
|
|
|
PRBool nsBlockFrame::IsLeftMostChild(nsIFrame* aFrame)
|
|
{
|
|
do {
|
|
nsIFrame* parent;
|
|
|
|
aFrame->GetGeometricParent(parent);
|
|
|
|
// See if there are any non-zero sized child frames that precede aFrame
|
|
// in the child list
|
|
nsIFrame* child;
|
|
|
|
parent->FirstChild(child);
|
|
while ((nsnull != child) && (aFrame != child)) {
|
|
nsSize size;
|
|
|
|
// Is the child zero-sized?
|
|
child->GetSize(size);
|
|
if ((size.width > 0) || (size.height > 0)) {
|
|
// We found a non-zero sized child frame that precedes aFrame
|
|
return PR_FALSE;
|
|
}
|
|
|
|
child->GetNextSibling(child);
|
|
}
|
|
|
|
// aFrame is the left-most non-zero sized frame in its geometric parent.
|
|
// Walk up one level and check that its parent is left-most as well
|
|
aFrame = parent;
|
|
} while (aFrame != this);
|
|
|
|
return PR_TRUE;
|
|
}
|
|
|
|
PRBool nsBlockFrame::AddFloater(nsIPresContext* aCX,
|
|
nsIFrame* aFloater,
|
|
PlaceholderFrame* aPlaceholder)
|
|
{
|
|
// Get the frame associated with the space manager, and get its nsIAnchoredItems
|
|
// interface
|
|
nsIFrame* frame = mCurrentState->spaceManager->GetFrame();
|
|
nsIAnchoredItems* anchoredItems = nsnull;
|
|
|
|
frame->QueryInterface(kIAnchoredItemsIID, (void**)&anchoredItems);
|
|
NS_ASSERTION(nsnull != anchoredItems, "no anchored items interface");
|
|
|
|
if (nsnull != anchoredItems) {
|
|
anchoredItems->AddAnchoredItem(aFloater, nsIAnchoredItems::anHTMLFloater, this);
|
|
PlaceFloater(aCX, aFloater, aPlaceholder);
|
|
return PR_TRUE;
|
|
}
|
|
|
|
return PR_FALSE;
|
|
}
|
|
|
|
// XXX The size of the floater needs to be taken into consideration if we're
|
|
// computing a maximum element size
|
|
void nsBlockFrame::PlaceFloater(nsIPresContext* aCX,
|
|
nsIFrame* aFloater,
|
|
PlaceholderFrame* aPlaceholder)
|
|
{
|
|
// If the floater is the left-most non-zero size child frame then insert
|
|
// it before the current line; otherwise add it to the below-current-line
|
|
// todo list and we'll handle it when we flush out the line
|
|
if (IsLeftMostChild(aPlaceholder)) {
|
|
// Get the type of floater
|
|
nsIStyleContext* styleContext;
|
|
|
|
aFloater->GetStyleContext(aCX, styleContext);
|
|
nsStyleMolecule* mol = (nsStyleMolecule*)styleContext->GetData(kStyleMoleculeSID);
|
|
NS_RELEASE(styleContext);
|
|
|
|
if (!mCurrentState->isInline) {
|
|
// Get the current band for this line
|
|
GetAvailableSpaceBand(*mCurrentState, mCurrentState->y + mCurrentState->topMargin);
|
|
}
|
|
|
|
// Commit some space in the space manager
|
|
nsRect region;
|
|
|
|
aFloater->GetRect(region);
|
|
region.y = mCurrentState->currentBand->rects[0].y;
|
|
|
|
if (NS_STYLE_FLOAT_LEFT == mol->floats) {
|
|
region.x = mCurrentState->currentBand->rects[0].x;
|
|
} else {
|
|
NS_ASSERTION(NS_STYLE_FLOAT_RIGHT == mol->floats, "bad float type");
|
|
region.x = mCurrentState->currentBand->rects[0].XMost() - region.width;
|
|
}
|
|
|
|
// XXX Don't forget the floater's margins...
|
|
mCurrentState->spaceManager->Translate(mCurrentState->borderPadding.left, 0);
|
|
mCurrentState->spaceManager->AddRectRegion(region);
|
|
|
|
// Set the origin of the floater in world coordinates
|
|
nscoord worldX, worldY;
|
|
|
|
mCurrentState->spaceManager->GetTranslation(worldX, worldY);
|
|
aFloater->MoveTo(region.x + worldX, region.y + worldY);
|
|
mCurrentState->spaceManager->Translate(-mCurrentState->borderPadding.left, 0);
|
|
|
|
// Update the band of available space to reflect space taken up by the floater
|
|
GetAvailableSpaceBand(*mCurrentState, mCurrentState->y + mCurrentState->topMargin);
|
|
} else {
|
|
// Add the floater to our to-do list
|
|
mCurrentState->floaterToDo.AppendElement(aFloater);
|
|
}
|
|
}
|
|
|
|
NS_METHOD nsBlockFrame::ContentAppended(nsIPresShell* aShell,
|
|
nsIPresContext* aPresContext,
|
|
nsIContent* aContainer)
|
|
{
|
|
// Get the last-in-flow
|
|
nsBlockFrame* flow = (nsBlockFrame*)GetLastInFlow();
|
|
|
|
PRInt32 kidIndex = flow->NextChildOffset();
|
|
PRInt32 startIndex = kidIndex;
|
|
nsIFrame* kidFrame = nsnull;
|
|
nsIFrame* prevKidFrame;
|
|
|
|
flow->LastChild(prevKidFrame);
|
|
for (;;) {
|
|
// Get the next content object
|
|
nsIContent* kid = mContent->ChildAt(kidIndex);
|
|
if (nsnull == kid) {
|
|
break;
|
|
}
|
|
|
|
// Resolve style for the kid
|
|
nsIStyleContext* kidSC = aPresContext->ResolveStyleContextFor(kid, this);
|
|
nsStyleMolecule* kidMol = (nsStyleMolecule*)kidSC->GetData(kStyleMoleculeSID);
|
|
|
|
// Is it a floater?
|
|
if (kidMol->floats != NS_STYLE_FLOAT_NONE) {
|
|
PlaceholderFrame::NewFrame(&kidFrame, kid, kidIndex, this);
|
|
} else {
|
|
// Create initial frame for the child
|
|
nsIContentDelegate* kidDel;
|
|
nsresult fr;
|
|
switch (kidMol->display) {
|
|
case NS_STYLE_DISPLAY_BLOCK:
|
|
case NS_STYLE_DISPLAY_LIST_ITEM:
|
|
// Pseudo block frames do not contain other block elements
|
|
// unless the block element would be the first child.
|
|
if (IsPseudoFrame()) {
|
|
// Update last content offset now that we are done drawing
|
|
// children from our parent.
|
|
SetLastContentOffset(prevKidFrame);
|
|
|
|
// It better be true that we are not being asked to flow a
|
|
// block element as our first child. That means the body
|
|
// decided it needed a pseudo-frame when it shouldn't have.
|
|
NS_ASSERTION(nsnull != mFirstChild, "bad body");
|
|
|
|
NS_RELEASE(kidSC);
|
|
NS_RELEASE(kid);
|
|
|
|
return NS_OK;
|
|
}
|
|
// FALL THROUGH (and create frame)
|
|
|
|
case NS_STYLE_DISPLAY_INLINE:
|
|
kidDel = kid->GetDelegate(aPresContext);
|
|
kidFrame = kidDel->CreateFrame(aPresContext, kid, kidIndex, this);
|
|
NS_RELEASE(kidDel);
|
|
break;
|
|
|
|
default:
|
|
fr = nsFrame::NewFrame(&kidFrame, kid, kidIndex, this);
|
|
break;
|
|
}
|
|
}
|
|
kidFrame->SetStyleContext(kidSC);
|
|
NS_RELEASE(kidSC);
|
|
NS_RELEASE(kid);
|
|
|
|
// Link child frame into the list of children
|
|
if (nsnull != prevKidFrame) {
|
|
#ifdef NS_DEBUG
|
|
nsIFrame* nextSibling;
|
|
|
|
prevKidFrame->GetNextSibling(nextSibling);
|
|
NS_ASSERTION(nsnull == nextSibling, "bad append");
|
|
#endif
|
|
prevKidFrame->SetNextSibling(kidFrame);
|
|
} else {
|
|
NS_ASSERTION(nsnull == mFirstChild, "bad create");
|
|
mFirstChild = kidFrame;
|
|
SetFirstContentOffset(kidFrame);
|
|
}
|
|
prevKidFrame = kidFrame;
|
|
kidIndex++;
|
|
mChildCount++;
|
|
}
|
|
SetLastContentOffset(prevKidFrame);
|
|
|
|
// If this is a pseudo-frame then our parent will generate the
|
|
// reflow command. Otherwise, if the container is us then we should
|
|
// generate the reflow command because we were directly called.
|
|
if (!IsPseudoFrame() && (aContainer == mContent)) {
|
|
nsReflowCommand* rc =
|
|
new nsReflowCommand(aPresContext, flow, nsReflowCommand::FrameAppended,
|
|
startIndex);
|
|
aShell->AppendReflowCommand(rc);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
PRIntn nsBlockFrame::GetSkipSides() const
|
|
{
|
|
PRIntn skip = 0;
|
|
if (nsnull != mPrevInFlow) {
|
|
skip |= 1 << NS_SIDE_TOP;
|
|
}
|
|
if (nsnull != mNextInFlow) {
|
|
skip |= 1 << NS_SIDE_BOTTOM;
|
|
}
|
|
return skip;
|
|
}
|
|
|
|
nsHTMLFrameType nsBlockFrame::GetFrameType() const
|
|
{
|
|
return eHTMLFrame_Block;
|
|
}
|
|
|
|
NS_METHOD nsBlockFrame::ListTag(FILE* out) const
|
|
{
|
|
if ((nsnull != mGeometricParent) && IsPseudoFrame()) {
|
|
fprintf(out, "*block(%d)@%p", mIndexInParent, this);
|
|
} else {
|
|
nsHTMLContainerFrame::ListTag(out);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifdef NS_DEBUG
|
|
void nsBlockFrame::DumpFlow() const
|
|
{
|
|
#ifdef NOISY_FLOW
|
|
nsBlockFrame* flow = (nsBlockFrame*) mNextInFlow;
|
|
while (flow != 0) {
|
|
printf(" %p: [%d,%d,%c]\n",
|
|
flow, flow->mFirstContentOffset, flow->mLastContentOffset,
|
|
(flow->mLastContentIsComplete ? 'T' : 'F'));
|
|
flow = (nsBlockFrame*) flow->mNextInFlow;
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|