Make the line list doubly linked and access it through a list class and iterators. Stop recomputing margins on all of the children of each block in the reflow chain (which causes O(N^2) state recovery during incremental reflow). Instead, add a second dirty bit to the lines and walk backwards through the line list to recompute vertical margins only when either dirty bit is set and the previous line was not reflowed. Add nsIFrame::IsEmpty to identify frames through which margins collapse. Fix O(N^2) propagation of float damage by maintaining a set of intervals damaged by floats (bug 61962) and be sure to damage the correct areas (bug 48138). Introduce nsCollapsingMargin to do correct collapsing of combinations of positive and negative margins (bug 50142). Clean up some odds and ends and fix another smaller O(N^2) problem in nsBlockFrame::AddFrames. r=attinasi, rbs sr=waterson git-svn-id: svn://10.0.0.236/trunk@106224 18797224-902f-48f8-a5cc-f745e15eee43
1147 lines
28 KiB
C++
1147 lines
28 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
// vim:cindent:ts=2:et:sw=2:
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.1 (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 the Initial Developer are Copyright (C) 1998
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* L. David Baron <dbaron@fas.harvard.edu>
|
|
* Pierre Phaneuf <pp@ludusdesign.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the NPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the NPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
#include "nsLineBox.h"
|
|
#include "nsISpaceManager.h"
|
|
#include "nsIStyleContext.h"
|
|
#include "nsLineLayout.h"
|
|
#include "prprf.h"
|
|
#include "nsBlockFrame.h"
|
|
#include "nsITextContent.h"
|
|
#include "nsLayoutAtoms.h"
|
|
|
|
#ifdef DEBUG
|
|
#include "nsISizeOfHandler.h"
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
static PRInt32 ctorCount;
|
|
PRInt32 nsLineBox::GetCtorCount() { return ctorCount; }
|
|
#endif
|
|
|
|
MOZ_DECL_CTOR_COUNTER(nsLineBox)
|
|
|
|
nsLineBox::nsLineBox(nsIFrame* aFrame, PRInt32 aCount, PRBool aIsBlock)
|
|
: mFirstChild(aFrame),
|
|
mBounds(0, 0, 0, 0),
|
|
mMaxElementWidth(0),
|
|
mMaximumWidth(-1),
|
|
mData(nsnull)
|
|
{
|
|
MOZ_COUNT_CTOR(nsLineBox);
|
|
#ifdef DEBUG
|
|
++ctorCount;
|
|
#endif
|
|
|
|
mAllFlags = 0;
|
|
#if NS_STYLE_CLEAR_NONE > 0
|
|
mFlags.mBreakType = NS_STYLE_CLEAR_NONE;
|
|
#endif
|
|
SetChildCount(aCount);
|
|
MarkDirty();
|
|
mFlags.mBlock = aIsBlock;
|
|
}
|
|
|
|
nsLineBox::~nsLineBox()
|
|
{
|
|
MOZ_COUNT_DTOR(nsLineBox);
|
|
Cleanup();
|
|
}
|
|
|
|
nsLineBox*
|
|
NS_NewLineBox(nsIPresShell* aPresShell, nsIFrame* aFrame,
|
|
PRInt32 aCount, PRBool aIsBlock)
|
|
{
|
|
return new (aPresShell)nsLineBox(aFrame, aCount, aIsBlock);
|
|
}
|
|
|
|
// Overloaded new operator. Uses an arena (which comes from the presShell)
|
|
// to perform the allocation.
|
|
void*
|
|
nsLineBox::operator new(size_t sz, nsIPresShell* aPresShell)
|
|
{
|
|
void* result = nsnull;
|
|
aPresShell->AllocateFrame(sz, &result);
|
|
return result;
|
|
}
|
|
|
|
// Overloaded delete operator. Doesn't actually free the memory, because we
|
|
// use an arena
|
|
void
|
|
nsLineBox::operator delete(void* aPtr, size_t sz)
|
|
{
|
|
}
|
|
|
|
void
|
|
nsLineBox::Destroy(nsIPresShell* aPresShell)
|
|
{
|
|
// Destroy the object. This won't actually free the memory, though
|
|
delete this;
|
|
|
|
// Have the pres shell recycle the memory
|
|
aPresShell->FreeFrame(sizeof(*this), (void*)this);
|
|
}
|
|
|
|
void
|
|
nsLineBox::Cleanup()
|
|
{
|
|
if (mData) {
|
|
if (IsBlock()) {
|
|
delete mBlockData;
|
|
}
|
|
else {
|
|
delete mInlineData;
|
|
}
|
|
mData = nsnull;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static void
|
|
ListFloaters(FILE* out, PRInt32 aIndent, const nsFloaterCacheList& aFloaters)
|
|
{
|
|
nsAutoString frameName;
|
|
nsFloaterCache* fc = aFloaters.Head();
|
|
while (fc) {
|
|
nsFrame::IndentBy(out, aIndent);
|
|
nsPlaceholderFrame* ph = fc->mPlaceholder;
|
|
if (nsnull != ph) {
|
|
fprintf(out, "placeholder@%p ", NS_STATIC_CAST(void*, ph));
|
|
nsIFrame* frame = ph->GetOutOfFlowFrame();
|
|
if (nsnull != frame) {
|
|
nsIFrameDebug* frameDebug;
|
|
|
|
if (NS_SUCCEEDED(frame->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**)&frameDebug))) {
|
|
frameDebug->GetFrameName(frameName);
|
|
fputs(NS_LossyConvertUCS2toASCII(frameName).get(), out);
|
|
}
|
|
}
|
|
fprintf(out, " %s region={%d,%d,%d,%d} combinedArea={%d,%d,%d,%d}",
|
|
fc->mIsCurrentLineFloater ? "cl" : "bcl",
|
|
fc->mRegion.x, fc->mRegion.y,
|
|
fc->mRegion.width, fc->mRegion.height,
|
|
fc->mCombinedArea.x, fc->mCombinedArea.y,
|
|
fc->mCombinedArea.width, fc->mCombinedArea.height);
|
|
|
|
fprintf(out, "\n");
|
|
}
|
|
fc = fc->Next();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
char*
|
|
nsLineBox::StateToString(char* aBuf, PRInt32 aBufSize) const
|
|
{
|
|
PR_snprintf(aBuf, aBufSize, "%s,%s,%s,%s[0x%x]",
|
|
IsBlock() ? "block" : "inline",
|
|
IsDirty() ? "dirty" : "clean",
|
|
IsPreviousMarginDirty() ? "prevmargindirty" : "prevmarginclean",
|
|
IsImpactedByFloater() ? "IMPACTED" : "NOT Impacted",
|
|
mAllFlags);
|
|
return aBuf;
|
|
}
|
|
|
|
void
|
|
nsLineBox::List(nsIPresContext* aPresContext, FILE* out, PRInt32 aIndent) const
|
|
{
|
|
PRInt32 i;
|
|
|
|
for (i = aIndent; --i >= 0; ) fputs(" ", out);
|
|
char cbuf[100];
|
|
fprintf(out, "line %p: count=%d state=%s ",
|
|
NS_STATIC_CAST(const void*, this), GetChildCount(),
|
|
StateToString(cbuf, sizeof(cbuf)));
|
|
if (IsBlock() && !GetCarriedOutBottomMargin().IsZero()) {
|
|
fprintf(out, "bm=%d ", GetCarriedOutBottomMargin().get());
|
|
}
|
|
if (0 != mMaxElementWidth) {
|
|
fprintf(out, "mew=%d ", mMaxElementWidth);
|
|
}
|
|
fprintf(out, "{%d,%d,%d,%d} ",
|
|
mBounds.x, mBounds.y, mBounds.width, mBounds.height);
|
|
if (mData) {
|
|
fprintf(out, "ca={%d,%d,%d,%d} ",
|
|
mData->mCombinedArea.x, mData->mCombinedArea.y,
|
|
mData->mCombinedArea.width, mData->mCombinedArea.height);
|
|
}
|
|
fprintf(out, "<\n");
|
|
|
|
nsIFrame* frame = mFirstChild;
|
|
PRInt32 n = GetChildCount();
|
|
while (--n >= 0) {
|
|
nsIFrameDebug* frameDebug;
|
|
|
|
if (NS_SUCCEEDED(frame->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**)&frameDebug))) {
|
|
frameDebug->List(aPresContext, out, aIndent + 1);
|
|
}
|
|
frame->GetNextSibling(&frame);
|
|
}
|
|
|
|
for (i = aIndent; --i >= 0; ) fputs(" ", out);
|
|
if (HasFloaters()) {
|
|
fputs("> floaters <\n", out);
|
|
ListFloaters(out, aIndent + 1, mInlineData->mFloaters);
|
|
for (i = aIndent; --i >= 0; ) fputs(" ", out);
|
|
}
|
|
fputs(">\n", out);
|
|
}
|
|
#endif
|
|
|
|
nsIFrame*
|
|
nsLineBox::LastChild() const
|
|
{
|
|
nsIFrame* frame = mFirstChild;
|
|
PRInt32 n = GetChildCount() - 1;
|
|
while (--n >= 0) {
|
|
frame->GetNextSibling(&frame);
|
|
}
|
|
return frame;
|
|
}
|
|
|
|
PRBool
|
|
nsLineBox::IsLastChild(nsIFrame* aFrame) const
|
|
{
|
|
nsIFrame* lastFrame = LastChild();
|
|
return aFrame == lastFrame;
|
|
}
|
|
|
|
PRInt32
|
|
nsLineBox::IndexOf(nsIFrame* aFrame) const
|
|
{
|
|
PRInt32 i, n = GetChildCount();
|
|
nsIFrame* frame = mFirstChild;
|
|
for (i = 0; i < n; i++) {
|
|
if (frame == aFrame) {
|
|
return i;
|
|
}
|
|
frame->GetNextSibling(&frame);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
nsresult
|
|
nsLineBox::IsEmpty(PRBool aIsQuirkMode, PRBool aParentIsPre,
|
|
PRBool *aResult) const
|
|
{
|
|
if (IsBlock())
|
|
return mFirstChild->IsEmpty(aIsQuirkMode, aParentIsPre, aResult);
|
|
|
|
*aResult = PR_TRUE;
|
|
PRInt32 n;
|
|
nsIFrame *kid;
|
|
for (n = GetChildCount(), kid = mFirstChild;
|
|
n > 0;
|
|
--n, kid->GetNextSibling(&kid))
|
|
{
|
|
kid->IsEmpty(aIsQuirkMode, aParentIsPre, aResult);
|
|
if (! *aResult)
|
|
break;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsLineBox::DeleteLineList(nsIPresContext* aPresContext, nsLineList& aLines)
|
|
{
|
|
if (! aLines.empty()) {
|
|
// Delete our child frames before doing anything else. In particular
|
|
// we do all of this before our base class releases it's hold on the
|
|
// view.
|
|
for (nsIFrame* child = aLines.front()->mFirstChild; child; ) {
|
|
nsIFrame* nextChild;
|
|
child->GetNextSibling(&nextChild);
|
|
child->Destroy(aPresContext);
|
|
child = nextChild;
|
|
}
|
|
|
|
while (! aLines.empty()) {
|
|
nsLineBox* line = aLines.front();
|
|
aLines.pop_front();
|
|
delete line;
|
|
}
|
|
}
|
|
}
|
|
|
|
nsLineBox*
|
|
nsLineBox::FindLineContaining(nsLineList& aLines, nsIFrame* aFrame,
|
|
PRInt32* aFrameIndexInLine)
|
|
{
|
|
NS_PRECONDITION(aFrameIndexInLine && !aLines.empty() && aFrame, "null ptr");
|
|
for (nsLineList::iterator line = aLines.begin(),
|
|
line_end = aLines.end();
|
|
line != line_end;
|
|
++line)
|
|
{
|
|
PRInt32 ix = line->IndexOf(aFrame);
|
|
if (ix >= 0) {
|
|
*aFrameIndexInLine = ix;
|
|
return line;
|
|
}
|
|
}
|
|
*aFrameIndexInLine = -1;
|
|
return nsnull;
|
|
}
|
|
|
|
PRBool
|
|
nsLineBox::RFindLineContaining(nsIFrame* aFrame,
|
|
const nsLineList::iterator& aBegin,
|
|
nsLineList::iterator& aEnd,
|
|
PRInt32* aFrameIndexInLine)
|
|
{
|
|
NS_PRECONDITION(aFrame, "null ptr");
|
|
while (aBegin != aEnd) {
|
|
--aEnd;
|
|
PRInt32 ix = aEnd->IndexOf(aFrame);
|
|
if (ix >= 0) {
|
|
*aFrameIndexInLine = ix;
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
*aFrameIndexInLine = -1;
|
|
return PR_FALSE;
|
|
}
|
|
|
|
nsCollapsingMargin
|
|
nsLineBox::GetCarriedOutBottomMargin() const
|
|
{
|
|
NS_ASSERTION(IsBlock(),
|
|
"GetCarriedOutBottomMargin called on non-block line.");
|
|
return (IsBlock() && mBlockData)
|
|
? mBlockData->mCarriedOutBottomMargin
|
|
: nsCollapsingMargin();
|
|
}
|
|
|
|
void
|
|
nsLineBox::SetCarriedOutBottomMargin(nsCollapsingMargin aValue)
|
|
{
|
|
if (IsBlock()) {
|
|
if (! aValue.IsZero()) {
|
|
if (!mBlockData) {
|
|
mBlockData = new ExtraBlockData(mBounds);
|
|
}
|
|
if (mBlockData) {
|
|
mBlockData->mCarriedOutBottomMargin = aValue;
|
|
}
|
|
}
|
|
else if (mBlockData) {
|
|
mBlockData->mCarriedOutBottomMargin = aValue;
|
|
MaybeFreeData();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsLineBox::MaybeFreeData()
|
|
{
|
|
if (mData && (mData->mCombinedArea == mBounds)) {
|
|
if (IsInline()) {
|
|
if (mInlineData->mFloaters.IsEmpty()) {
|
|
delete mInlineData;
|
|
mInlineData = nsnull;
|
|
}
|
|
}
|
|
else if (mBlockData->mCarriedOutBottomMargin.IsZero()) {
|
|
delete mBlockData;
|
|
mBlockData = nsnull;
|
|
}
|
|
}
|
|
}
|
|
|
|
// XXX get rid of this???
|
|
nsFloaterCache*
|
|
nsLineBox::GetFirstFloater()
|
|
{
|
|
NS_ABORT_IF_FALSE(IsInline(), "block line can't have floaters");
|
|
return mInlineData ? mInlineData->mFloaters.Head() : nsnull;
|
|
}
|
|
|
|
// XXX this might be too eager to free memory
|
|
void
|
|
nsLineBox::FreeFloaters(nsFloaterCacheFreeList& aFreeList)
|
|
{
|
|
NS_ABORT_IF_FALSE(IsInline(), "block line can't have floaters");
|
|
if (IsInline()) {
|
|
if (mInlineData) {
|
|
aFreeList.Append(mInlineData->mFloaters);
|
|
MaybeFreeData();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsLineBox::RemoveFloatersFromSpaceManager(nsISpaceManager* aSpaceManager)
|
|
{
|
|
if (IsInline()) {
|
|
if (mInlineData) {
|
|
nsFloaterCache* floaterCache = mInlineData->mFloaters.Head();
|
|
|
|
while (floaterCache) {
|
|
nsIFrame* floater = floaterCache->mPlaceholder->GetOutOfFlowFrame();
|
|
aSpaceManager->RemoveRegion(floater);
|
|
floaterCache = floaterCache->Next();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsLineBox::AppendFloaters(nsFloaterCacheFreeList& aFreeList)
|
|
{
|
|
NS_ABORT_IF_FALSE(IsInline(), "block line can't have floaters");
|
|
if (IsInline()) {
|
|
if (aFreeList.NotEmpty()) {
|
|
if (!mInlineData) {
|
|
mInlineData = new ExtraInlineData(mBounds);
|
|
}
|
|
if (mInlineData) {
|
|
mInlineData->mFloaters.Append(aFreeList);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PRBool
|
|
nsLineBox::RemoveFloater(nsIFrame* aFrame)
|
|
{
|
|
NS_ABORT_IF_FALSE(IsInline(), "block line can't have floaters");
|
|
if (IsInline() && mInlineData) {
|
|
nsFloaterCache* fc = mInlineData->mFloaters.Find(aFrame);
|
|
if (fc) {
|
|
// Note: the placeholder is part of the line's child list
|
|
// and will be removed later.
|
|
fc->mPlaceholder->SetOutOfFlowFrame(nsnull);
|
|
mInlineData->mFloaters.Remove(fc);
|
|
MaybeFreeData();
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
void
|
|
nsLineBox::SetCombinedArea(const nsRect& aCombinedArea)
|
|
{
|
|
NS_ASSERTION(aCombinedArea.width >= 0, "illegal width for combined area");
|
|
NS_ASSERTION(aCombinedArea.height >= 0, "illegal height for combined area");
|
|
if (aCombinedArea != mBounds) {
|
|
if (mData) {
|
|
mData->mCombinedArea = aCombinedArea;
|
|
}
|
|
else {
|
|
if (IsInline()) {
|
|
mInlineData = new ExtraInlineData(aCombinedArea);
|
|
}
|
|
else {
|
|
mBlockData = new ExtraBlockData(aCombinedArea);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (mData) {
|
|
// Store away new value so that MaybeFreeData compares against
|
|
// the right value.
|
|
mData->mCombinedArea = aCombinedArea;
|
|
}
|
|
MaybeFreeData();
|
|
}
|
|
#ifdef VERY_NOISY_REFLOW
|
|
printf("nsLB::SetCombinedArea(1) %p (%d, %d, %d, %d)\n",
|
|
this, aCombinedArea.x, aCombinedArea.y, aCombinedArea.width, aCombinedArea.height);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
nsLineBox::GetCombinedArea(nsRect* aResult)
|
|
{
|
|
NS_ASSERTION(aResult, "null arg");
|
|
if (aResult) {
|
|
*aResult = mData ? mData->mCombinedArea : mBounds;
|
|
#ifdef VERY_NOISY_REFLOW
|
|
printf("nsLB::SetCombinedArea(1) %p (%d, %d, %d, %d)\n",
|
|
this, aResult->x, aResult->y, aResult->width, aResult->height);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
nsIAtom*
|
|
nsLineBox::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const
|
|
{
|
|
NS_PRECONDITION(aResult, "null OUT parameter pointer");
|
|
*aResult = sizeof(*this);
|
|
|
|
nsIAtom* atom;
|
|
if (IsBlock()) {
|
|
atom = nsLayoutAtoms::lineBoxBlockSmall;
|
|
if (mBlockData) {
|
|
atom = nsLayoutAtoms::lineBoxBlockBig;
|
|
*aResult += sizeof(*mBlockData);
|
|
}
|
|
}
|
|
else {
|
|
atom = nsLayoutAtoms::lineBoxSmall;
|
|
if (mInlineData) {
|
|
atom = nsLayoutAtoms::lineBoxBig;
|
|
*aResult += sizeof(*mInlineData);
|
|
|
|
// Add in the size needed for floaters associated with this line
|
|
if (HasFloaters()) {
|
|
PRUint32 floatersSize;
|
|
mInlineData->mFloaters.SizeOf(aHandler, &floatersSize);
|
|
|
|
// Base size of embedded object was included in sizeof(*this) above
|
|
floatersSize -= sizeof(mInlineData->mFloaters);
|
|
aHandler->AddSize(nsLayoutAtoms::lineBoxFloaters, floatersSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
return atom;
|
|
}
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
|
|
static nsLineBox* gDummyLines[1];
|
|
|
|
nsLineIterator::nsLineIterator()
|
|
{
|
|
NS_INIT_REFCNT();
|
|
mLines = gDummyLines;
|
|
mNumLines = 0;
|
|
mIndex = 0;
|
|
mRightToLeft = PR_FALSE;
|
|
}
|
|
|
|
nsLineIterator::~nsLineIterator()
|
|
{
|
|
if (mLines != gDummyLines) {
|
|
delete [] mLines;
|
|
}
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS2(nsLineIterator, nsILineIterator, nsILineIteratorNavigator)
|
|
|
|
nsresult
|
|
nsLineIterator::Init(nsLineList& aLines, PRBool aRightToLeft)
|
|
{
|
|
mRightToLeft = aRightToLeft;
|
|
|
|
// Count the lines
|
|
PRInt32 numLines = aLines.size();
|
|
if (0 == numLines) {
|
|
// Use gDummyLines so that we don't need null pointer checks in
|
|
// the accessor methods
|
|
mLines = gDummyLines;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Make a linear array of the lines
|
|
mLines = new nsLineBox*[numLines];
|
|
if (!mLines) {
|
|
// Use gDummyLines so that we don't need null pointer checks in
|
|
// the accessor methods
|
|
mLines = gDummyLines;
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
nsLineBox** lp = mLines;
|
|
for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end() ;
|
|
line != line_end;
|
|
++line)
|
|
{
|
|
*lp++ = line;
|
|
}
|
|
mNumLines = numLines;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLineIterator::GetNumLines(PRInt32* aResult)
|
|
{
|
|
NS_PRECONDITION(aResult, "null OUT ptr");
|
|
if (!aResult) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
*aResult = mNumLines;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLineIterator::GetDirection(PRBool* aIsRightToLeft)
|
|
{
|
|
NS_PRECONDITION(aIsRightToLeft, "null OUT ptr");
|
|
if (!aIsRightToLeft) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
*aIsRightToLeft = mRightToLeft;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLineIterator::GetLine(PRInt32 aLineNumber,
|
|
nsIFrame** aFirstFrameOnLine,
|
|
PRInt32* aNumFramesOnLine,
|
|
nsRect& aLineBounds,
|
|
PRUint32* aLineFlags)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aFirstFrameOnLine);
|
|
NS_ENSURE_ARG_POINTER(aNumFramesOnLine);
|
|
NS_ENSURE_ARG_POINTER(aLineFlags);
|
|
|
|
if ((aLineNumber < 0) || (aLineNumber >= mNumLines)) {
|
|
*aFirstFrameOnLine = nsnull;
|
|
*aNumFramesOnLine = 0;
|
|
aLineBounds.SetRect(0, 0, 0, 0);
|
|
return NS_OK;
|
|
}
|
|
nsLineBox* line = mLines[aLineNumber];
|
|
*aFirstFrameOnLine = line->mFirstChild;
|
|
*aNumFramesOnLine = line->GetChildCount();
|
|
aLineBounds = line->mBounds;
|
|
|
|
PRUint32 flags = 0;
|
|
if (line->IsBlock()) {
|
|
flags |= NS_LINE_FLAG_IS_BLOCK;
|
|
}
|
|
else {
|
|
if (line->HasBreak())
|
|
flags |= NS_LINE_FLAG_ENDS_IN_BREAK;
|
|
}
|
|
*aLineFlags = flags;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLineIterator::FindLineContaining(nsIFrame* aFrame,
|
|
PRInt32* aLineNumberResult)
|
|
{
|
|
nsLineBox* line = mLines[0];
|
|
PRInt32 lineNumber = 0;
|
|
while (lineNumber != mNumLines) {
|
|
if (line->Contains(aFrame)) {
|
|
*aLineNumberResult = lineNumber;
|
|
return NS_OK;
|
|
}
|
|
line = mLines[++lineNumber];
|
|
}
|
|
*aLineNumberResult = -1;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLineIterator::FindLineAt(nscoord aY,
|
|
PRInt32* aLineNumberResult)
|
|
{
|
|
nsLineBox* line = mLines[0];
|
|
if (!line || (aY < line->mBounds.y)) {
|
|
*aLineNumberResult = -1;
|
|
return NS_OK;
|
|
}
|
|
PRInt32 lineNumber = 0;
|
|
while (lineNumber != mNumLines) {
|
|
if ((aY >= line->mBounds.y) && (aY < line->mBounds.YMost())) {
|
|
*aLineNumberResult = lineNumber;
|
|
return NS_OK;
|
|
}
|
|
line = mLines[++lineNumber];
|
|
}
|
|
*aLineNumberResult = mNumLines;
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifdef IBMBIDI
|
|
NS_IMETHODIMP
|
|
nsLineIterator::CheckLineOrder(PRInt32 aLine,
|
|
PRBool *aIsReordered,
|
|
nsIFrame **aFirstVisual,
|
|
nsIFrame **aLastVisual)
|
|
{
|
|
nsRect checkRect;
|
|
PRInt32 currentLine, saveLine, testLine;
|
|
nscoord saveX;
|
|
nsIFrame *checkFrame;
|
|
nsIFrame *firstFrame;
|
|
nsIFrame *leftmostFrame;
|
|
nsIFrame *rightmostFrame;
|
|
nscoord minX, maxX;
|
|
PRInt32 lineFrameCount;
|
|
PRUint32 lineFlags;
|
|
|
|
nsresult result = NS_OK;
|
|
|
|
// an RTL paragraph is always considered as reordered
|
|
// in an LTR paragraph, find out by examining the coordinates of each frame in the line
|
|
if (mRightToLeft)
|
|
*aIsReordered = PR_TRUE;
|
|
else {
|
|
*aIsReordered = PR_FALSE;
|
|
|
|
// Check the preceding and following line, since we might be moving into them
|
|
for (currentLine = PR_MAX(0, aLine-1); currentLine < aLine+1; currentLine++) {
|
|
|
|
nsLineBox* line = mLines[currentLine];
|
|
if (!line)
|
|
break;
|
|
|
|
checkFrame = line->mFirstChild;
|
|
|
|
checkFrame->GetRect(checkRect);
|
|
result = FindLineContaining(checkFrame, &saveLine);
|
|
if (NS_FAILED(result))
|
|
return result;
|
|
saveX = checkRect.x;
|
|
lineFrameCount = line->GetChildCount();
|
|
|
|
for (; checkFrame; result = checkFrame->GetNextSibling(&checkFrame)) {
|
|
if (NS_FAILED(result))
|
|
break;
|
|
result = FindLineContaining(checkFrame, &testLine);
|
|
if (NS_FAILED(result))
|
|
return result;
|
|
if (testLine != saveLine) {
|
|
*aIsReordered = PR_TRUE;
|
|
break;
|
|
}
|
|
|
|
checkFrame->GetRect(checkRect);
|
|
// If the origin of any frame is less than the previous frame, the line is reordered
|
|
if (checkRect.x < saveX) {
|
|
*aIsReordered = PR_TRUE;
|
|
break;
|
|
}
|
|
saveX = checkRect.x;
|
|
lineFrameCount--;
|
|
if (0 == lineFrameCount)
|
|
break;
|
|
}
|
|
if (*aIsReordered)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the line is reordered, identify the first and last frames on the line
|
|
if (*aIsReordered) {
|
|
nsRect nonUsedRect;
|
|
result = GetLine(aLine, &firstFrame, &lineFrameCount, nonUsedRect, &lineFlags);
|
|
if (NS_FAILED(result))
|
|
return result;
|
|
|
|
leftmostFrame = rightmostFrame = firstFrame;
|
|
firstFrame->GetRect(checkRect);
|
|
maxX = checkRect.x;
|
|
minX = checkRect.x;
|
|
|
|
for (;lineFrameCount > 1;lineFrameCount--) {
|
|
result = firstFrame->GetNextSibling(&firstFrame);
|
|
|
|
if (NS_FAILED(result)){
|
|
NS_ASSERTION(0,"should not be reached nsLineBox\n");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
firstFrame->GetRect(checkRect);
|
|
if (checkRect.x > maxX) {
|
|
maxX = checkRect.x;
|
|
rightmostFrame = firstFrame;
|
|
}
|
|
if (checkRect.x < minX) {
|
|
minX = checkRect.x;
|
|
leftmostFrame = firstFrame;
|
|
}
|
|
}
|
|
if (mRightToLeft) {
|
|
*aFirstVisual = rightmostFrame;
|
|
*aLastVisual = leftmostFrame;
|
|
}
|
|
else {
|
|
*aFirstVisual = leftmostFrame;
|
|
*aLastVisual = rightmostFrame;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endif // IBMBIDI
|
|
|
|
NS_IMETHODIMP
|
|
nsLineIterator::FindFrameAt(PRInt32 aLineNumber,
|
|
nscoord aX,
|
|
#ifdef IBMBIDI
|
|
PRBool aCouldBeReordered,
|
|
#endif // IBMBIDI
|
|
nsIFrame** aFrameFound,
|
|
PRBool* aXIsBeforeFirstFrame,
|
|
PRBool* aXIsAfterLastFrame)
|
|
{
|
|
NS_PRECONDITION(aFrameFound && aXIsBeforeFirstFrame && aXIsAfterLastFrame,
|
|
"null OUT ptr");
|
|
if (!aFrameFound || !aXIsBeforeFirstFrame || !aXIsAfterLastFrame) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
if ((aLineNumber < 0) || (aLineNumber >= mNumLines)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsLineBox* line = mLines[aLineNumber];
|
|
if (!line) {
|
|
*aFrameFound = nsnull;
|
|
*aXIsBeforeFirstFrame = PR_TRUE;
|
|
*aXIsAfterLastFrame = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (aX < line->mBounds.x) {
|
|
nsIFrame* frame;
|
|
if (mRightToLeft) {
|
|
frame = line->LastChild();
|
|
}
|
|
else {
|
|
frame = line->mFirstChild;
|
|
}
|
|
*aFrameFound = frame;
|
|
*aXIsBeforeFirstFrame = PR_TRUE;
|
|
*aXIsAfterLastFrame = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
else if (aX >= line->mBounds.XMost()) {
|
|
nsIFrame* frame;
|
|
if (mRightToLeft) {
|
|
frame = line->mFirstChild;
|
|
}
|
|
else {
|
|
frame = line->LastChild();
|
|
}
|
|
*aFrameFound = frame;
|
|
*aXIsBeforeFirstFrame = PR_FALSE;
|
|
*aXIsAfterLastFrame = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Find the frame closest to the X coordinate. Gaps can occur
|
|
// between frames (because of margins) so we split the gap in two
|
|
// when checking.
|
|
*aXIsBeforeFirstFrame = PR_FALSE;
|
|
*aXIsAfterLastFrame = PR_FALSE;
|
|
#ifdef IBMBIDI
|
|
PRBool isReordered = PR_FALSE;
|
|
nsIFrame *firstVisual, *lastVisual;
|
|
if (aCouldBeReordered)
|
|
CheckLineOrder(aLineNumber, &isReordered, &firstVisual, &lastVisual);
|
|
#endif
|
|
nsRect r1, r2;
|
|
nsIFrame* frame = line->mFirstChild;
|
|
#ifdef IBMBIDI
|
|
if (isReordered)
|
|
frame = firstVisual;
|
|
#endif // IBMBIDI
|
|
PRInt32 n = line->GetChildCount();
|
|
if (mRightToLeft) {
|
|
while (--n >= 0) {
|
|
nsIFrame* nextFrame;
|
|
#ifdef IBMBIDI
|
|
if (!frame)
|
|
break;
|
|
if (isReordered) {
|
|
nscoord maxX, limX;
|
|
PRInt32 testLine;
|
|
nsRect tempRect;
|
|
nsIFrame* tempFrame;
|
|
|
|
maxX = -0x7fffffff;
|
|
frame->GetRect(tempRect);
|
|
|
|
limX = tempRect.x;
|
|
tempFrame = line->mFirstChild;
|
|
nextFrame = nsnull;
|
|
|
|
while (tempFrame) {
|
|
if (NS_SUCCEEDED(FindLineContaining(tempFrame, &testLine))
|
|
&& testLine == aLineNumber) {
|
|
tempFrame->GetRect(tempRect);
|
|
if (tempRect.x > maxX && tempRect.x < limX) { // we are looking for the highest value less than the current one
|
|
maxX = tempRect.x;
|
|
nextFrame = tempFrame;
|
|
}
|
|
}
|
|
tempFrame->GetNextSibling(&tempFrame);
|
|
}
|
|
}
|
|
else
|
|
#endif // IBMBIDI
|
|
frame->GetNextSibling(&nextFrame);
|
|
frame->GetRect(r1);
|
|
if (aX > r1.x) {
|
|
break;
|
|
}
|
|
if (nextFrame) {
|
|
nextFrame->GetRect(r2);
|
|
if (aX > r2.XMost()) {
|
|
nscoord rightEdge = r2.XMost();
|
|
nscoord delta = r1.x - rightEdge;
|
|
if (aX < rightEdge + delta/2) {
|
|
frame = nextFrame;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
*aXIsBeforeFirstFrame = PR_TRUE;
|
|
}
|
|
frame = nextFrame;
|
|
}
|
|
}
|
|
else {
|
|
while (--n >= 0) {
|
|
nsIFrame* nextFrame;
|
|
#ifdef IBMBIDI
|
|
if (!frame)
|
|
break;
|
|
if (isReordered) {
|
|
nsRect tempRect;
|
|
nsIFrame* tempFrame;
|
|
nscoord minX, limX;
|
|
PRInt32 testLine;
|
|
|
|
minX = 0x7fffffff;
|
|
frame->GetRect(tempRect);
|
|
|
|
limX = tempRect.x;
|
|
tempFrame = line->mFirstChild;
|
|
nextFrame = nsnull;
|
|
|
|
while (tempFrame) {
|
|
if (NS_SUCCEEDED(FindLineContaining(tempFrame, &testLine))
|
|
&& testLine == aLineNumber) {
|
|
tempFrame->GetRect(tempRect);
|
|
if (tempRect.x < minX && tempRect.x > limX) { // we are looking for the lowest value greater than the current one
|
|
minX = tempRect.x;
|
|
nextFrame = tempFrame;
|
|
}
|
|
}
|
|
tempFrame->GetNextSibling(&tempFrame);
|
|
}
|
|
}
|
|
else
|
|
#endif // IBMBIDI
|
|
frame->GetNextSibling(&nextFrame);
|
|
frame->GetRect(r1);
|
|
if (aX < r1.XMost()) {
|
|
break;
|
|
}
|
|
if (nextFrame) {
|
|
nextFrame->GetRect(r2);
|
|
if (aX < r2.x) {
|
|
nscoord rightEdge = r1.XMost();
|
|
nscoord delta = r2.x - rightEdge;
|
|
if (aX >= rightEdge + delta/2) {
|
|
frame = nextFrame;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
*aXIsAfterLastFrame = PR_TRUE;
|
|
}
|
|
frame = nextFrame;
|
|
}
|
|
}
|
|
|
|
*aFrameFound = frame;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsLineIterator::GetNextSiblingOnLine(nsIFrame*& aFrame, PRInt32 aLineNumber)
|
|
{
|
|
return aFrame->GetNextSibling(&aFrame);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nsFloaterCacheList::~nsFloaterCacheList()
|
|
{
|
|
nsFloaterCache* floater = mHead;
|
|
while (floater) {
|
|
nsFloaterCache* next = floater->mNext;
|
|
delete floater;
|
|
floater = next;
|
|
}
|
|
mHead = nsnull;
|
|
}
|
|
|
|
nsFloaterCache*
|
|
nsFloaterCacheList::Tail() const
|
|
{
|
|
nsFloaterCache* fc = mHead;
|
|
while (fc) {
|
|
if (!fc->mNext) {
|
|
break;
|
|
}
|
|
fc = fc->mNext;
|
|
}
|
|
return fc;
|
|
}
|
|
|
|
void
|
|
nsFloaterCacheList::Append(nsFloaterCacheFreeList& aList)
|
|
{
|
|
nsFloaterCache* tail = Tail();
|
|
if (tail) {
|
|
tail->mNext = aList.mHead;
|
|
}
|
|
else {
|
|
mHead = aList.mHead;
|
|
}
|
|
aList.mHead = nsnull;
|
|
aList.mTail = nsnull;
|
|
}
|
|
|
|
nsFloaterCache*
|
|
nsFloaterCacheList::Find(nsIFrame* aOutOfFlowFrame)
|
|
{
|
|
nsFloaterCache* fc = mHead;
|
|
while (fc) {
|
|
if (fc->mPlaceholder->GetOutOfFlowFrame() == aOutOfFlowFrame) {
|
|
break;
|
|
}
|
|
fc = fc->Next();
|
|
}
|
|
return fc;
|
|
}
|
|
|
|
void
|
|
nsFloaterCacheList::Remove(nsFloaterCache* aElement)
|
|
{
|
|
nsFloaterCache** fcp = &mHead;
|
|
nsFloaterCache* fc;
|
|
while (nsnull != (fc = *fcp)) {
|
|
if (fc == aElement) {
|
|
*fcp = fc->mNext;
|
|
break;
|
|
}
|
|
fcp = &fc->mNext;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void
|
|
nsFloaterCacheList::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const
|
|
{
|
|
NS_PRECONDITION(aResult, "null OUT parameter pointer");
|
|
*aResult = sizeof(*this);
|
|
|
|
// Add in the space for each floater
|
|
for (nsFloaterCache* cache = Head(); cache; cache = cache->Next()) {
|
|
*aResult += sizeof(*cache);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
void
|
|
nsFloaterCacheFreeList::Append(nsFloaterCacheList& aList)
|
|
{
|
|
if (mTail) {
|
|
mTail->mNext = aList.mHead;
|
|
}
|
|
else {
|
|
mHead = aList.mHead;
|
|
}
|
|
mTail = aList.Tail();
|
|
aList.mHead = nsnull;
|
|
}
|
|
|
|
nsFloaterCache*
|
|
nsFloaterCacheFreeList::Alloc()
|
|
{
|
|
nsFloaterCache* fc = mHead;
|
|
if (mHead) {
|
|
if (mHead == mTail) {
|
|
mHead = mTail = nsnull;
|
|
}
|
|
else {
|
|
mHead = fc->mNext;
|
|
}
|
|
fc->mNext = nsnull;
|
|
}
|
|
else {
|
|
fc = new nsFloaterCache();
|
|
}
|
|
return fc;
|
|
}
|
|
|
|
void
|
|
nsFloaterCacheFreeList::Append(nsFloaterCache* aFloater)
|
|
{
|
|
aFloater->mNext = nsnull;
|
|
if (mTail) {
|
|
mTail->mNext = aFloater;
|
|
mTail = aFloater;
|
|
}
|
|
else {
|
|
mHead = mTail = aFloater;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
MOZ_DECL_CTOR_COUNTER(nsFloaterCache)
|
|
|
|
nsFloaterCache::nsFloaterCache()
|
|
: mPlaceholder(nsnull),
|
|
mIsCurrentLineFloater(PR_TRUE),
|
|
mMargins(0, 0, 0, 0),
|
|
mOffsets(0, 0, 0, 0),
|
|
mCombinedArea(0, 0, 0, 0),
|
|
mNext(nsnull)
|
|
{
|
|
MOZ_COUNT_CTOR(nsFloaterCache);
|
|
}
|
|
|
|
#ifdef NS_BUILD_REFCNT_LOGGING
|
|
nsFloaterCache::~nsFloaterCache()
|
|
{
|
|
MOZ_COUNT_DTOR(nsFloaterCache);
|
|
}
|
|
#endif
|