mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-11 16:32:59 +00:00
1891 lines
56 KiB
C++
1891 lines
56 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "nsRegion.h"
|
|
#include "nsISupportsImpl.h"
|
|
#include "nsTArray.h"
|
|
#include "mozilla/ThreadLocal.h"
|
|
#include "nsPrintfCString.h"
|
|
|
|
/*
|
|
* The SENTINEL values below guaranties that a < or >
|
|
* comparison with it will be false for all values of the
|
|
* underlying nscoord type. E.g. this is always false:
|
|
* aCoord > NS_COORD_GREATER_SENTINEL
|
|
* Setting the mRectListHead dummy rectangle to these values
|
|
* allows us to loop without checking for the list end.
|
|
*/
|
|
#ifdef NS_COORD_IS_FLOAT
|
|
#define NS_COORD_LESS_SENTINEL nscoord_MIN
|
|
#define NS_COORD_GREATER_SENTINEL nscoord_MAX
|
|
#else
|
|
#define NS_COORD_LESS_SENTINEL INT32_MIN
|
|
#define NS_COORD_GREATER_SENTINEL INT32_MAX
|
|
#endif
|
|
|
|
// Fast inline analogues of nsRect methods for nsRegion::nsRectFast.
|
|
// Check for emptiness is not required - it is guaranteed by caller.
|
|
|
|
inline bool nsRegion::nsRectFast::Contains (const nsRect& aRect) const
|
|
{
|
|
return (bool) ((aRect.x >= x) && (aRect.y >= y) &&
|
|
(aRect.XMost () <= XMost ()) && (aRect.YMost () <= YMost ()));
|
|
}
|
|
|
|
inline bool nsRegion::nsRectFast::Intersects (const nsRect& aRect) const
|
|
{
|
|
return (bool) ((x < aRect.XMost ()) && (y < aRect.YMost ()) &&
|
|
(aRect.x < XMost ()) && (aRect.y < YMost ()));
|
|
}
|
|
|
|
inline bool nsRegion::nsRectFast::IntersectRect (const nsRect& aRect1, const nsRect& aRect2)
|
|
{
|
|
const nscoord xmost = NS_MIN (aRect1.XMost (), aRect2.XMost ());
|
|
x = NS_MAX (aRect1.x, aRect2.x);
|
|
width = xmost - x;
|
|
if (width <= 0) return false;
|
|
|
|
const nscoord ymost = NS_MIN (aRect1.YMost (), aRect2.YMost ());
|
|
y = NS_MAX (aRect1.y, aRect2.y);
|
|
height = ymost - y;
|
|
if (height <= 0) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
inline void nsRegion::nsRectFast::UnionRect (const nsRect& aRect1, const nsRect& aRect2)
|
|
{
|
|
const nscoord xmost = NS_MAX (aRect1.XMost (), aRect2.XMost ());
|
|
const nscoord ymost = NS_MAX (aRect1.YMost (), aRect2.YMost ());
|
|
x = NS_MIN(aRect1.x, aRect2.x);
|
|
y = NS_MIN(aRect1.y, aRect2.y);
|
|
width = xmost - x;
|
|
height = ymost - y;
|
|
}
|
|
|
|
|
|
|
|
// Custom memory allocator for nsRegion::RgnRect structures.
|
|
// Entries are allocated from global memory pool.
|
|
// Memory pool can grow in size, but it can't shrink.
|
|
|
|
#define INIT_MEM_CHUNK_ENTRIES 100
|
|
#define INCR_MEM_CHUNK_ENTRIES 100
|
|
|
|
class RgnRectMemoryAllocator
|
|
{
|
|
nsRegion::RgnRect* mFreeListHead;
|
|
uint32_t mFreeEntries;
|
|
void* mChunkListHead;
|
|
#if defined (DEBUG)
|
|
NS_DECL_OWNINGTHREAD
|
|
|
|
void InitLock () { NS_ASSERT_OWNINGTHREAD (RgnRectMemoryAllocator); }
|
|
void DestroyLock () { NS_ASSERT_OWNINGTHREAD (RgnRectMemoryAllocator); }
|
|
void Lock () { NS_ASSERT_OWNINGTHREAD (RgnRectMemoryAllocator); }
|
|
void Unlock () { NS_ASSERT_OWNINGTHREAD (RgnRectMemoryAllocator); }
|
|
#else
|
|
void InitLock () { }
|
|
void DestroyLock () { }
|
|
void Lock () { }
|
|
void Unlock () { }
|
|
#endif
|
|
|
|
void* AllocChunk (uint32_t aEntries, void* aNextChunk, nsRegion::RgnRect* aTailDest)
|
|
{
|
|
uint8_t* pBuf = new uint8_t [aEntries * sizeof (nsRegion::RgnRect) + sizeof (void*)];
|
|
*reinterpret_cast<void**>(pBuf) = aNextChunk;
|
|
nsRegion::RgnRect* pRect = reinterpret_cast<nsRegion::RgnRect*>(pBuf + sizeof (void*));
|
|
|
|
for (uint32_t cnt = 0 ; cnt < aEntries - 1 ; cnt++)
|
|
pRect [cnt].next = &pRect [cnt + 1];
|
|
|
|
pRect [aEntries - 1].next = aTailDest;
|
|
|
|
return pBuf;
|
|
}
|
|
|
|
void FreeChunk (void* aChunk) { delete [] (uint8_t *) aChunk; }
|
|
void* NextChunk (void* aThisChunk) const { return *static_cast<void**>(aThisChunk); }
|
|
|
|
nsRegion::RgnRect* ChunkHead (void* aThisChunk) const
|
|
{ return reinterpret_cast<nsRegion::RgnRect*>(static_cast<uint8_t*>(aThisChunk) + sizeof (void*)); }
|
|
|
|
public:
|
|
RgnRectMemoryAllocator (uint32_t aNumOfEntries);
|
|
~RgnRectMemoryAllocator ();
|
|
|
|
nsRegion::RgnRect* Alloc ();
|
|
void Free (nsRegion::RgnRect* aRect);
|
|
};
|
|
|
|
|
|
RgnRectMemoryAllocator::RgnRectMemoryAllocator (uint32_t aNumOfEntries)
|
|
{
|
|
InitLock ();
|
|
mChunkListHead = AllocChunk (aNumOfEntries, nullptr, nullptr);
|
|
mFreeEntries = aNumOfEntries;
|
|
mFreeListHead = ChunkHead (mChunkListHead);
|
|
}
|
|
|
|
RgnRectMemoryAllocator::~RgnRectMemoryAllocator ()
|
|
{
|
|
while (mChunkListHead)
|
|
{
|
|
void* tmp = mChunkListHead;
|
|
mChunkListHead = NextChunk (mChunkListHead);
|
|
FreeChunk (tmp);
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* As a static object this class outlives any library which would implement
|
|
* locking. So we intentionally leak the 'lock'.
|
|
*
|
|
* Currently RgnRectMemoryAllocator is only used from the primary thread,
|
|
* so we aren't using a lock which means that there is no lock to leak.
|
|
* If we ever switch to multiple GUI threads (e.g. one per window),
|
|
* we'd probably use one allocator per window-thread to avoid the
|
|
* locking overhead and just require consumers not to pass regions
|
|
* across threads/windows, which would be a reasonable restriction
|
|
* because they wouldn't be useful outside their window.
|
|
*/
|
|
DestroyLock ();
|
|
#endif
|
|
}
|
|
|
|
nsRegion::RgnRect* RgnRectMemoryAllocator::Alloc ()
|
|
{
|
|
Lock ();
|
|
|
|
if (mFreeEntries == 0)
|
|
{
|
|
mChunkListHead = AllocChunk (INCR_MEM_CHUNK_ENTRIES, mChunkListHead, mFreeListHead);
|
|
mFreeEntries = INCR_MEM_CHUNK_ENTRIES;
|
|
mFreeListHead = ChunkHead (mChunkListHead);
|
|
}
|
|
|
|
nsRegion::RgnRect* tmp = mFreeListHead;
|
|
mFreeListHead = mFreeListHead->next;
|
|
mFreeEntries--;
|
|
Unlock ();
|
|
|
|
return tmp;
|
|
}
|
|
|
|
void RgnRectMemoryAllocator::Free (nsRegion::RgnRect* aRect)
|
|
{
|
|
Lock ();
|
|
mFreeEntries++;
|
|
aRect->next = mFreeListHead;
|
|
mFreeListHead = aRect;
|
|
Unlock ();
|
|
}
|
|
|
|
|
|
// Global pool for nsRegion::RgnRect allocation
|
|
mozilla::ThreadLocal<RgnRectMemoryAllocator*> gRectPoolTlsIndex;
|
|
|
|
void RgnRectMemoryAllocatorDTOR(void *priv)
|
|
{
|
|
RgnRectMemoryAllocator* allocator = gRectPoolTlsIndex.get();
|
|
delete allocator;
|
|
}
|
|
|
|
nsresult nsRegion::InitStatic()
|
|
{
|
|
return gRectPoolTlsIndex.init() ? NS_OK : NS_ERROR_FAILURE;
|
|
}
|
|
|
|
void nsRegion::ShutdownStatic()
|
|
{
|
|
RgnRectMemoryAllocator* allocator = gRectPoolTlsIndex.get();
|
|
if (!allocator)
|
|
return;
|
|
|
|
delete allocator;
|
|
|
|
gRectPoolTlsIndex.set(nullptr);
|
|
}
|
|
|
|
void* nsRegion::RgnRect::operator new (size_t) CPP_THROW_NEW
|
|
{
|
|
RgnRectMemoryAllocator* allocator = gRectPoolTlsIndex.get();
|
|
if (!allocator) {
|
|
allocator = new RgnRectMemoryAllocator(INIT_MEM_CHUNK_ENTRIES);
|
|
gRectPoolTlsIndex.set(allocator);
|
|
}
|
|
return allocator->Alloc ();
|
|
}
|
|
|
|
void nsRegion::RgnRect::operator delete (void* aRect, size_t)
|
|
{
|
|
RgnRectMemoryAllocator* allocator = gRectPoolTlsIndex.get();
|
|
if (!allocator) {
|
|
NS_ERROR("Invalid nsRegion::RgnRect delete");
|
|
return;
|
|
}
|
|
allocator->Free (static_cast<RgnRect*>(aRect));
|
|
}
|
|
|
|
|
|
|
|
void nsRegion::Init()
|
|
{
|
|
mRectListHead.prev = mRectListHead.next = &mRectListHead;
|
|
mCurRect = &mRectListHead;
|
|
mRectCount = 0;
|
|
mBoundRect.SetRect (0, 0, 0, 0);
|
|
}
|
|
|
|
inline void nsRegion::InsertBefore (RgnRect* aNewRect, RgnRect* aRelativeRect)
|
|
{
|
|
aNewRect->prev = aRelativeRect->prev;
|
|
aNewRect->next = aRelativeRect;
|
|
aRelativeRect->prev->next = aNewRect;
|
|
aRelativeRect->prev = aNewRect;
|
|
mCurRect = aNewRect;
|
|
mRectCount++;
|
|
}
|
|
|
|
inline void nsRegion::InsertAfter (RgnRect* aNewRect, RgnRect* aRelativeRect)
|
|
{
|
|
aNewRect->prev = aRelativeRect;
|
|
aNewRect->next = aRelativeRect->next;
|
|
aRelativeRect->next->prev = aNewRect;
|
|
aRelativeRect->next = aNewRect;
|
|
mCurRect = aNewRect;
|
|
mRectCount++;
|
|
}
|
|
|
|
|
|
// Adjust the number of rectangles in region.
|
|
// Content of rectangles should be changed by caller.
|
|
|
|
void nsRegion::SetToElements (uint32_t aCount)
|
|
{
|
|
if (mRectCount < aCount) // Add missing rectangles
|
|
{
|
|
uint32_t InsertCount = aCount - mRectCount;
|
|
mRectCount = aCount;
|
|
RgnRect* pPrev = &mRectListHead;
|
|
RgnRect* pNext = mRectListHead.next;
|
|
|
|
while (InsertCount--)
|
|
{
|
|
mCurRect = new RgnRect;
|
|
mCurRect->prev = pPrev;
|
|
pPrev->next = mCurRect;
|
|
pPrev = mCurRect;
|
|
}
|
|
|
|
pPrev->next = pNext;
|
|
pNext->prev = pPrev;
|
|
} else
|
|
if (mRectCount > aCount) // Remove unnecessary rectangles
|
|
{
|
|
uint32_t RemoveCount = mRectCount - aCount;
|
|
mRectCount = aCount;
|
|
mCurRect = mRectListHead.next;
|
|
|
|
while (RemoveCount--)
|
|
{
|
|
RgnRect* tmp = mCurRect;
|
|
mCurRect = mCurRect->next;
|
|
delete tmp;
|
|
}
|
|
|
|
mRectListHead.next = mCurRect;
|
|
mCurRect->prev = &mRectListHead;
|
|
}
|
|
}
|
|
|
|
|
|
// Save the entire chain of linked elements in 'prev' field of the RgnRect structure.
|
|
// After that forward-only iterations using 'next' field could still be used.
|
|
// Some elements from forward-only chain could be temporarily removed to optimize inner loops.
|
|
// The original double linked state could be restored by call to RestoreLinkChain ().
|
|
// Both functions despite size can be inline because they are called only from one function.
|
|
|
|
inline void nsRegion::SaveLinkChain ()
|
|
{
|
|
RgnRect* pRect = &mRectListHead;
|
|
|
|
do
|
|
{
|
|
pRect->prev = pRect->next;
|
|
pRect = pRect->next;
|
|
} while (pRect != &mRectListHead);
|
|
}
|
|
|
|
|
|
inline void nsRegion::RestoreLinkChain ()
|
|
{
|
|
RgnRect* pPrev = &mRectListHead;
|
|
RgnRect* pRect = mRectListHead.next = mRectListHead.prev;
|
|
|
|
while (pRect != &mRectListHead)
|
|
{
|
|
pRect->next = pRect->prev;
|
|
pRect->prev = pPrev;
|
|
pPrev = pRect;
|
|
pRect = pRect->next;
|
|
}
|
|
|
|
mRectListHead.prev = pPrev;
|
|
}
|
|
|
|
|
|
// Insert node in right place of sorted list
|
|
// If necessary then bounding rectangle could be updated and rectangle combined
|
|
// with neighbour rectangles. This is usually done in Optimize ()
|
|
|
|
void nsRegion::InsertInPlace (RgnRect* aRect, bool aOptimizeOnFly)
|
|
{
|
|
if (mRectCount == 0)
|
|
InsertAfter (aRect, &mRectListHead);
|
|
else
|
|
{
|
|
if (aRect->y > mCurRect->y)
|
|
{
|
|
mRectListHead.y = NS_COORD_GREATER_SENTINEL;
|
|
while (aRect->y > mCurRect->next->y)
|
|
mCurRect = mCurRect->next;
|
|
|
|
mRectListHead.x = NS_COORD_GREATER_SENTINEL;
|
|
while (aRect->y == mCurRect->next->y && aRect->x > mCurRect->next->x)
|
|
mCurRect = mCurRect->next;
|
|
|
|
InsertAfter (aRect, mCurRect);
|
|
} else
|
|
if (aRect->y < mCurRect->y)
|
|
{
|
|
mRectListHead.y = NS_COORD_LESS_SENTINEL;
|
|
while (aRect->y < mCurRect->prev->y)
|
|
mCurRect = mCurRect->prev;
|
|
|
|
mRectListHead.x = NS_COORD_LESS_SENTINEL;
|
|
while (aRect->y == mCurRect->prev->y && aRect->x < mCurRect->prev->x)
|
|
mCurRect = mCurRect->prev;
|
|
|
|
InsertBefore (aRect, mCurRect);
|
|
} else
|
|
{
|
|
if (aRect->x > mCurRect->x)
|
|
{
|
|
mRectListHead.x = NS_COORD_GREATER_SENTINEL;
|
|
while (aRect->y == mCurRect->next->y && aRect->x > mCurRect->next->x)
|
|
mCurRect = mCurRect->next;
|
|
|
|
InsertAfter (aRect, mCurRect);
|
|
} else
|
|
{
|
|
mRectListHead.x = NS_COORD_LESS_SENTINEL;
|
|
while (aRect->y == mCurRect->prev->y && aRect->x < mCurRect->prev->x)
|
|
mCurRect = mCurRect->prev;
|
|
|
|
InsertBefore (aRect, mCurRect);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (aOptimizeOnFly)
|
|
{
|
|
if (mRectCount == 1)
|
|
mBoundRect = *mCurRect;
|
|
else
|
|
{
|
|
mBoundRect.UnionRect (mBoundRect, *mCurRect);
|
|
|
|
// Check if we can go left or up before starting to combine rectangles
|
|
if ((mCurRect->y == mCurRect->prev->y && mCurRect->height == mCurRect->prev->height &&
|
|
mCurRect->x == mCurRect->prev->XMost ()) ||
|
|
(mCurRect->x == mCurRect->prev->x && mCurRect->width == mCurRect->prev->width &&
|
|
mCurRect->y == mCurRect->prev->YMost ()) )
|
|
mCurRect = mCurRect->prev;
|
|
|
|
// Try to combine with rectangle on right side
|
|
while (mCurRect->y == mCurRect->next->y && mCurRect->height == mCurRect->next->height &&
|
|
mCurRect->XMost () == mCurRect->next->x)
|
|
{
|
|
mCurRect->width += mCurRect->next->width;
|
|
delete Remove (mCurRect->next);
|
|
}
|
|
|
|
// Try to combine with rectangle under this one
|
|
while (mCurRect->x == mCurRect->next->x && mCurRect->width == mCurRect->next->width &&
|
|
mCurRect->YMost () == mCurRect->next->y)
|
|
{
|
|
mCurRect->height += mCurRect->next->height;
|
|
delete Remove (mCurRect->next);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
nsRegion::RgnRect* nsRegion::Remove (RgnRect* aRect)
|
|
{
|
|
aRect->prev->next = aRect->next;
|
|
aRect->next->prev = aRect->prev;
|
|
mRectCount--;
|
|
|
|
if (mCurRect == aRect)
|
|
mCurRect = (aRect->next != &mRectListHead) ? aRect->next : aRect->prev;
|
|
|
|
return aRect;
|
|
}
|
|
|
|
|
|
// Try to reduce the number of rectangles in complex region by combining with
|
|
// surrounding ones on right and bottom sides of each rectangle in list.
|
|
// Update bounding rectangle
|
|
|
|
void nsRegion::Optimize ()
|
|
{
|
|
if (mRectCount == 0)
|
|
mBoundRect.SetRect (0, 0, 0, 0);
|
|
else
|
|
{
|
|
RgnRect* pRect = mRectListHead.next;
|
|
int32_t xmost = mRectListHead.prev->XMost ();
|
|
int32_t ymost = mRectListHead.prev->YMost ();
|
|
mBoundRect.x = mRectListHead.next->x;
|
|
mBoundRect.y = mRectListHead.next->y;
|
|
|
|
while (pRect != &mRectListHead)
|
|
{
|
|
// Try to combine with rectangle on right side
|
|
while (pRect->y == pRect->next->y && pRect->height == pRect->next->height &&
|
|
pRect->XMost () == pRect->next->x)
|
|
{
|
|
pRect->width += pRect->next->width;
|
|
delete Remove (pRect->next);
|
|
}
|
|
|
|
// Try to combine with rectangle under this one
|
|
while (pRect->x == pRect->next->x && pRect->width == pRect->next->width &&
|
|
pRect->YMost () == pRect->next->y)
|
|
{
|
|
pRect->height += pRect->next->height;
|
|
delete Remove (pRect->next);
|
|
}
|
|
|
|
// Determine bound rectangle. Use fact that rectangles are sorted.
|
|
if (pRect->x < mBoundRect.x) mBoundRect.x = pRect->x;
|
|
if (pRect->XMost () > xmost) xmost = pRect->XMost ();
|
|
if (pRect->YMost () > ymost) ymost = pRect->YMost ();
|
|
|
|
pRect = pRect->next;
|
|
}
|
|
|
|
mBoundRect.width = xmost - mBoundRect.x;
|
|
mBoundRect.height = ymost - mBoundRect.y;
|
|
}
|
|
}
|
|
|
|
|
|
// Move rectangles starting from 'aStartRect' till end of the list to the destionation region.
|
|
// Important for temporary objects - instead of copying rectangles with Merge () and then
|
|
// emptying region in destructor they could be moved to destination region in one step.
|
|
|
|
void nsRegion::MoveInto (nsRegion& aDestRegion, const RgnRect* aStartRect)
|
|
{
|
|
RgnRect* pRect = const_cast<RgnRect*>(aStartRect);
|
|
RgnRect* pPrev = pRect->prev;
|
|
|
|
while (pRect != &mRectListHead)
|
|
{
|
|
RgnRect* next = pRect->next;
|
|
aDestRegion.InsertInPlace (pRect);
|
|
|
|
mRectCount--;
|
|
pRect = next;
|
|
}
|
|
|
|
pPrev->next = &mRectListHead;
|
|
mRectListHead.prev = pPrev;
|
|
mCurRect = mRectListHead.next;
|
|
}
|
|
|
|
|
|
// Merge two non-overlapping regions into one.
|
|
// Automatically optimize region by calling Optimize ()
|
|
|
|
void nsRegion::Merge (const nsRegion& aRgn1, const nsRegion& aRgn2)
|
|
{
|
|
if (aRgn1.mRectCount == 0) // Region empty. Result is equal to other region
|
|
Copy (aRgn2);
|
|
else
|
|
if (aRgn2.mRectCount == 0) // Region empty. Result is equal to other region
|
|
Copy (aRgn1);
|
|
if (aRgn1.mRectCount == 1) // Region is single rectangle. Optimize on fly
|
|
{
|
|
RgnRect* TmpRect = new RgnRect (*aRgn1.mRectListHead.next);
|
|
Copy (aRgn2);
|
|
InsertInPlace (TmpRect, true);
|
|
} else
|
|
if (aRgn2.mRectCount == 1) // Region is single rectangle. Optimize on fly
|
|
{
|
|
RgnRect* TmpRect = new RgnRect (*aRgn2.mRectListHead.next);
|
|
Copy (aRgn1);
|
|
InsertInPlace (TmpRect, true);
|
|
} else
|
|
{
|
|
const nsRegion* pCopyRegion, *pInsertRegion;
|
|
|
|
// Determine which region contains more rectangles. Copy the larger one
|
|
if (aRgn1.mRectCount >= aRgn2.mRectCount)
|
|
{
|
|
pCopyRegion = &aRgn1;
|
|
pInsertRegion = &aRgn2;
|
|
} else
|
|
{
|
|
pCopyRegion = &aRgn2;
|
|
pInsertRegion = &aRgn1;
|
|
}
|
|
|
|
if (pInsertRegion == this) // Do merge in-place
|
|
pInsertRegion = pCopyRegion;
|
|
else
|
|
Copy (*pCopyRegion);
|
|
|
|
const RgnRect* pSrcRect = pInsertRegion->mRectListHead.next;
|
|
|
|
while (pSrcRect != &pInsertRegion->mRectListHead)
|
|
{
|
|
InsertInPlace (new RgnRect (*pSrcRect));
|
|
|
|
pSrcRect = pSrcRect->next;
|
|
}
|
|
|
|
Optimize ();
|
|
}
|
|
}
|
|
|
|
|
|
nsRegion& nsRegion::Copy (const nsRegion& aRegion)
|
|
{
|
|
if (&aRegion == this)
|
|
return *this;
|
|
|
|
if (aRegion.mRectCount == 0)
|
|
SetEmpty ();
|
|
else
|
|
{
|
|
SetToElements (aRegion.mRectCount);
|
|
|
|
const RgnRect* pSrc = aRegion.mRectListHead.next;
|
|
RgnRect* pDest = mRectListHead.next;
|
|
|
|
while (pSrc != &aRegion.mRectListHead)
|
|
{
|
|
*pDest = *pSrc;
|
|
|
|
pSrc = pSrc->next;
|
|
pDest = pDest->next;
|
|
}
|
|
|
|
mCurRect = mRectListHead.next;
|
|
mBoundRect = aRegion.mBoundRect;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
nsRegion& nsRegion::Copy (const nsRect& aRect)
|
|
{
|
|
if (aRect.IsEmpty ())
|
|
SetEmpty ();
|
|
else
|
|
{
|
|
SetToElements (1);
|
|
*mRectListHead.next = static_cast<const RgnRect&>(aRect);
|
|
mBoundRect = static_cast<const nsRectFast&>(aRect);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
nsRegion& nsRegion::And (const nsRegion& aRgn1, const nsRegion& aRgn2)
|
|
{
|
|
if (&aRgn1 == &aRgn2) // And with self
|
|
Copy (aRgn1);
|
|
else
|
|
if (aRgn1.mRectCount == 0 || aRgn2.mRectCount == 0) // If either region is empty then result is empty
|
|
SetEmpty ();
|
|
else
|
|
{
|
|
nsRectFast TmpRect;
|
|
|
|
if (aRgn1.mRectCount == 1 && aRgn2.mRectCount == 1) // Intersect rectangle with rectangle
|
|
{
|
|
TmpRect.IntersectRect (*aRgn1.mRectListHead.next, *aRgn2.mRectListHead.next);
|
|
Copy (TmpRect);
|
|
} else
|
|
{
|
|
if (!aRgn1.mBoundRect.Intersects (aRgn2.mBoundRect)) // Regions do not intersect
|
|
SetEmpty ();
|
|
else
|
|
{
|
|
// Region is simple rectangle and it fully overlays other region
|
|
if (aRgn1.mRectCount == 1 && aRgn1.mBoundRect.Contains (aRgn2.mBoundRect))
|
|
Copy (aRgn2);
|
|
else
|
|
// Region is simple rectangle and it fully overlays other region
|
|
if (aRgn2.mRectCount == 1 && aRgn2.mBoundRect.Contains (aRgn1.mBoundRect))
|
|
Copy (aRgn1);
|
|
else
|
|
{
|
|
nsRegion TmpRegion;
|
|
nsRegion* pSrcRgn1 = const_cast<nsRegion*>(&aRgn1);
|
|
nsRegion* pSrcRgn2 = const_cast<nsRegion*>(&aRgn2);
|
|
|
|
if (&aRgn1 == this) // Copy region if it is both source and result
|
|
{
|
|
TmpRegion.Copy (aRgn1);
|
|
pSrcRgn1 = &TmpRegion;
|
|
}
|
|
|
|
if (&aRgn2 == this) // Copy region if it is both source and result
|
|
{
|
|
TmpRegion.Copy (aRgn2);
|
|
pSrcRgn2 = &TmpRegion;
|
|
}
|
|
|
|
// For outer loop prefer region for which at least one rectangle is below other's bound rectangle
|
|
if (pSrcRgn2->mRectListHead.prev->y >= pSrcRgn1->mBoundRect.YMost ())
|
|
{
|
|
nsRegion* Tmp = pSrcRgn1;
|
|
pSrcRgn1 = pSrcRgn2;
|
|
pSrcRgn2 = Tmp;
|
|
}
|
|
|
|
|
|
SetToElements (0);
|
|
pSrcRgn2->SaveLinkChain ();
|
|
|
|
pSrcRgn1->mRectListHead.y = NS_COORD_GREATER_SENTINEL;
|
|
pSrcRgn2->mRectListHead.y = NS_COORD_GREATER_SENTINEL;
|
|
|
|
for (RgnRect* pSrcRect1 = pSrcRgn1->mRectListHead.next ;
|
|
pSrcRect1->y < pSrcRgn2->mBoundRect.YMost () ; pSrcRect1 = pSrcRect1->next)
|
|
{
|
|
if (pSrcRect1->Intersects (pSrcRgn2->mBoundRect)) // Rectangle intersects region. Process each rectangle
|
|
{
|
|
RgnRect* pPrev2 = &pSrcRgn2->mRectListHead;
|
|
|
|
for (RgnRect* pSrcRect2 = pSrcRgn2->mRectListHead.next ;
|
|
pSrcRect2->y < pSrcRect1->YMost () ; pSrcRect2 = pSrcRect2->next)
|
|
{
|
|
if (pSrcRect2->YMost () <= pSrcRect1->y) // Rect2's bottom is above the top of Rect1.
|
|
{ // No successive rectangles in Rgn1 can intersect it.
|
|
pPrev2->next = pSrcRect2->next; // Remove Rect2 from Rgn2's checklist
|
|
continue;
|
|
}
|
|
|
|
if (pSrcRect1->Contains (*pSrcRect2)) // Rect1 fully overlays Rect2.
|
|
{ // No any other rectangle in Rgn1 can intersect it.
|
|
pPrev2->next = pSrcRect2->next; // Remove Rect2 from Rgn2's checklist
|
|
InsertInPlace (new RgnRect (*pSrcRect2));
|
|
continue;
|
|
}
|
|
|
|
|
|
if (TmpRect.IntersectRect (*pSrcRect1, *pSrcRect2))
|
|
InsertInPlace (new RgnRect (TmpRect));
|
|
|
|
pPrev2 = pSrcRect2;
|
|
}
|
|
}
|
|
}
|
|
|
|
pSrcRgn2->RestoreLinkChain ();
|
|
Optimize ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
nsRegion& nsRegion::And (const nsRegion& aRegion, const nsRect& aRect)
|
|
{
|
|
// If either region or rectangle is empty then result is empty
|
|
if (aRegion.mRectCount == 0 || aRect.IsEmpty ())
|
|
SetEmpty ();
|
|
else // Intersect region with rectangle
|
|
{
|
|
const nsRectFast& aRectFast = static_cast<const nsRectFast&>(aRect);
|
|
nsRectFast TmpRect;
|
|
|
|
if (aRegion.mRectCount == 1) // Intersect rectangle with rectangle
|
|
{
|
|
TmpRect.IntersectRect (*aRegion.mRectListHead.next, aRectFast);
|
|
Copy (TmpRect);
|
|
} else // Intersect complex region with rectangle
|
|
{
|
|
if (!aRectFast.Intersects (aRegion.mBoundRect)) // Rectangle does not intersect region
|
|
SetEmpty ();
|
|
else
|
|
{
|
|
if (aRectFast.Contains (aRegion.mBoundRect)) // Rectangle fully overlays region
|
|
Copy (aRegion);
|
|
else
|
|
{
|
|
nsRegion TmpRegion;
|
|
nsRegion* pSrcRegion = const_cast<nsRegion*>(&aRegion);
|
|
|
|
if (&aRegion == this) // Copy region if it is both source and result
|
|
{
|
|
TmpRegion.Copy (aRegion);
|
|
pSrcRegion = &TmpRegion;
|
|
}
|
|
|
|
SetToElements (0);
|
|
pSrcRegion->mRectListHead.y = NS_COORD_GREATER_SENTINEL;
|
|
|
|
for (const RgnRect* pSrcRect = pSrcRegion->mRectListHead.next ;
|
|
pSrcRect->y < aRectFast.YMost () ; pSrcRect = pSrcRect->next)
|
|
{
|
|
if (TmpRect.IntersectRect (*pSrcRect, aRectFast))
|
|
InsertInPlace (new RgnRect (TmpRect));
|
|
}
|
|
|
|
Optimize ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
nsRegion& nsRegion::Or (const nsRegion& aRgn1, const nsRegion& aRgn2)
|
|
{
|
|
if (&aRgn1 == &aRgn2) // Or with self
|
|
Copy (aRgn1);
|
|
else
|
|
if (aRgn1.mRectCount == 0) // Region empty. Result is equal to other region
|
|
Copy (aRgn2);
|
|
else
|
|
if (aRgn2.mRectCount == 0) // Region empty. Result is equal to other region
|
|
Copy (aRgn1);
|
|
else
|
|
{
|
|
if (!aRgn1.mBoundRect.Intersects (aRgn2.mBoundRect)) // Regions do not intersect
|
|
Merge (aRgn1, aRgn2);
|
|
else
|
|
{
|
|
// Region is simple rectangle and it fully overlays other region
|
|
if (aRgn1.mRectCount == 1 && aRgn1.mBoundRect.Contains (aRgn2.mBoundRect))
|
|
Copy (aRgn1);
|
|
else
|
|
// Region is simple rectangle and it fully overlays other region
|
|
if (aRgn2.mRectCount == 1 && aRgn2.mBoundRect.Contains (aRgn1.mBoundRect))
|
|
Copy (aRgn2);
|
|
else
|
|
{
|
|
nsRegion TmpRegion;
|
|
aRgn1.SubRegion (aRgn2, TmpRegion); // Get only parts of region which not overlap the other region
|
|
Copy (aRgn2);
|
|
TmpRegion.MoveInto (*this);
|
|
Optimize ();
|
|
}
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
nsRegion& nsRegion::Or (const nsRegion& aRegion, const nsRect& aRect)
|
|
{
|
|
if (aRegion.mRectCount == 0) // Region empty. Result is equal to rectangle
|
|
Copy (aRect);
|
|
else
|
|
if (aRect.IsEmpty ()) // Rectangle is empty. Result is equal to region
|
|
Copy (aRegion);
|
|
else
|
|
{
|
|
const nsRectFast& aRectFast = static_cast<const nsRectFast&>(aRect);
|
|
|
|
if (!aRectFast.Intersects (aRegion.mBoundRect)) // Rectangle does not intersect region
|
|
{
|
|
Copy (aRegion);
|
|
InsertInPlace (new RgnRect (aRectFast), true);
|
|
} else
|
|
{
|
|
// Region is simple rectangle and it fully overlays rectangle
|
|
if (aRegion.mRectCount == 1 && aRegion.mBoundRect.Contains (aRectFast))
|
|
Copy (aRegion);
|
|
else
|
|
if (aRectFast.Contains (aRegion.mBoundRect)) // Rectangle fully overlays region
|
|
Copy (aRectFast);
|
|
else
|
|
{
|
|
aRegion.SubRect (aRectFast, *this); // Exclude from region parts that overlap the rectangle
|
|
InsertInPlace (new RgnRect (aRectFast));
|
|
Optimize ();
|
|
}
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
nsRegion& nsRegion::Xor (const nsRegion& aRgn1, const nsRegion& aRgn2)
|
|
{
|
|
if (&aRgn1 == &aRgn2) // Xor with self
|
|
SetEmpty ();
|
|
else
|
|
if (aRgn1.mRectCount == 0) // Region empty. Result is equal to other region
|
|
Copy (aRgn2);
|
|
else
|
|
if (aRgn2.mRectCount == 0) // Region empty. Result is equal to other region
|
|
Copy (aRgn1);
|
|
else
|
|
{
|
|
if (!aRgn1.mBoundRect.Intersects (aRgn2.mBoundRect)) // Regions do not intersect
|
|
Merge (aRgn1, aRgn2);
|
|
else
|
|
{
|
|
// Region is simple rectangle and it fully overlays other region
|
|
if (aRgn1.mRectCount == 1 && aRgn1.mBoundRect.Contains (aRgn2.mBoundRect))
|
|
{
|
|
aRgn1.SubRegion (aRgn2, *this);
|
|
Optimize ();
|
|
} else
|
|
// Region is simple rectangle and it fully overlays other region
|
|
if (aRgn2.mRectCount == 1 && aRgn2.mBoundRect.Contains (aRgn1.mBoundRect))
|
|
{
|
|
aRgn2.SubRegion (aRgn1, *this);
|
|
Optimize ();
|
|
} else
|
|
{
|
|
nsRegion TmpRegion;
|
|
aRgn1.SubRegion (aRgn2, TmpRegion);
|
|
aRgn2.SubRegion (aRgn1, *this);
|
|
TmpRegion.MoveInto (*this);
|
|
Optimize ();
|
|
}
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
nsRegion& nsRegion::Xor (const nsRegion& aRegion, const nsRect& aRect)
|
|
{
|
|
if (aRegion.mRectCount == 0) // Region empty. Result is equal to rectangle
|
|
Copy (aRect);
|
|
else
|
|
if (aRect.IsEmpty ()) // Rectangle is empty. Result is equal to region
|
|
Copy (aRegion);
|
|
else
|
|
{
|
|
const nsRectFast& aRectFast = static_cast<const nsRectFast&>(aRect);
|
|
|
|
if (!aRectFast.Intersects (aRegion.mBoundRect)) // Rectangle does not intersect region
|
|
{
|
|
Copy (aRegion);
|
|
InsertInPlace (new RgnRect (aRectFast), true);
|
|
} else
|
|
{
|
|
// Region is simple rectangle and it fully overlays rectangle
|
|
if (aRegion.mRectCount == 1 && aRegion.mBoundRect.Contains (aRectFast))
|
|
{
|
|
aRegion.SubRect (aRectFast, *this);
|
|
Optimize ();
|
|
} else
|
|
if (aRectFast.Contains (aRegion.mBoundRect)) // Rectangle fully overlays region
|
|
{
|
|
nsRegion TmpRegion;
|
|
TmpRegion.Copy (aRectFast);
|
|
TmpRegion.SubRegion (aRegion, *this);
|
|
Optimize ();
|
|
} else
|
|
{
|
|
nsRegion TmpRegion;
|
|
TmpRegion.Copy (aRectFast);
|
|
TmpRegion.SubRegion (aRegion, TmpRegion);
|
|
aRegion.SubRect (aRectFast, *this);
|
|
TmpRegion.MoveInto (*this);
|
|
Optimize ();
|
|
}
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
nsRegion& nsRegion::Sub (const nsRegion& aRgn1, const nsRegion& aRgn2)
|
|
{
|
|
if (&aRgn1 == &aRgn2) // Sub from self
|
|
SetEmpty ();
|
|
else
|
|
if (aRgn1.mRectCount == 0) // If source is empty then result is empty, too
|
|
SetEmpty ();
|
|
else
|
|
if (aRgn2.mRectCount == 0) // Nothing to subtract
|
|
Copy (aRgn1);
|
|
else
|
|
{
|
|
if (!aRgn1.mBoundRect.Intersects (aRgn2.mBoundRect)) // Regions do not intersect
|
|
Copy (aRgn1);
|
|
else
|
|
{
|
|
aRgn1.SubRegion (aRgn2, *this);
|
|
Optimize ();
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
nsRegion& nsRegion::Sub (const nsRegion& aRegion, const nsRect& aRect)
|
|
{
|
|
if (aRegion.mRectCount == 0) // If source is empty then result is empty, too
|
|
SetEmpty ();
|
|
else
|
|
if (aRect.IsEmpty ()) // Nothing to subtract
|
|
Copy (aRegion);
|
|
else
|
|
{
|
|
const nsRectFast& aRectFast = static_cast<const nsRectFast&>(aRect);
|
|
|
|
if (!aRectFast.Intersects (aRegion.mBoundRect)) // Rectangle does not intersect region
|
|
Copy (aRegion);
|
|
else
|
|
{
|
|
if (aRectFast.Contains (aRegion.mBoundRect)) // Rectangle fully overlays region
|
|
SetEmpty ();
|
|
else
|
|
{
|
|
aRegion.SubRect (aRectFast, *this);
|
|
Optimize ();
|
|
}
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool nsRegion::Contains (const nsRect& aRect) const
|
|
{
|
|
if (aRect.IsEmpty())
|
|
return true;
|
|
if (IsEmpty())
|
|
return false;
|
|
if (!IsComplex())
|
|
return mBoundRect.Contains (aRect);
|
|
|
|
nsRegion tmpRgn;
|
|
tmpRgn.Sub(aRect, *this);
|
|
return tmpRgn.IsEmpty();
|
|
}
|
|
|
|
bool nsRegion::Contains (const nsRegion& aRgn) const
|
|
{
|
|
// XXX this could be made faster
|
|
nsRegionRectIterator iter(aRgn);
|
|
while (const nsRect* r = iter.Next()) {
|
|
if (!Contains (*r)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool nsRegion::Intersects (const nsRect& aRect) const
|
|
{
|
|
if (aRect.IsEmpty() || IsEmpty())
|
|
return false;
|
|
|
|
const RgnRect* r = mRectListHead.next;
|
|
while (r != &mRectListHead)
|
|
{
|
|
if (r->Intersects(aRect))
|
|
return true;
|
|
r = r->next;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Subtract region from current region.
|
|
// Both regions are non-empty and they intersect each other.
|
|
// Result could be empty region if aRgn2 is rectangle that fully overlays aRgn1.
|
|
// Optimize () is not called on exit (bound rectangle is not updated).
|
|
|
|
void nsRegion::SubRegion (const nsRegion& aRegion, nsRegion& aResult) const
|
|
{
|
|
if (aRegion.mRectCount == 1) // Subtract simple rectangle
|
|
{
|
|
if (aRegion.mBoundRect.Contains (mBoundRect))
|
|
aResult.SetEmpty ();
|
|
else
|
|
SubRect (*aRegion.mRectListHead.next, aResult);
|
|
} else
|
|
{
|
|
nsRegion TmpRegion, CompletedRegion;
|
|
const nsRegion* pSubRgn = &aRegion;
|
|
|
|
if (&aResult == &aRegion) // Copy region if it is both source and result
|
|
{
|
|
TmpRegion.Copy (aRegion);
|
|
pSubRgn = &TmpRegion;
|
|
}
|
|
|
|
const RgnRect* pSubRect = pSubRgn->mRectListHead.next;
|
|
|
|
SubRect (*pSubRect, aResult, CompletedRegion);
|
|
pSubRect = pSubRect->next;
|
|
|
|
while (pSubRect != &pSubRgn->mRectListHead)
|
|
{
|
|
aResult.SubRect (*pSubRect, aResult, CompletedRegion);
|
|
pSubRect = pSubRect->next;
|
|
}
|
|
|
|
CompletedRegion.MoveInto (aResult);
|
|
}
|
|
}
|
|
|
|
|
|
// Subtract rectangle from current region.
|
|
// Both region and rectangle are non-empty and they intersect each other.
|
|
// Result could be empty region if aRect fully overlays aRegion.
|
|
// Could be called repeatedly with 'this' as input and result - bound rectangle is not known.
|
|
// Optimize () is not called on exit (bound rectangle is not updated).
|
|
//
|
|
// aCompleted is filled with rectangles which are already checked and could be safely
|
|
// removed from further examination in case aRect rectangles come from ordered list.
|
|
// aCompleted is not automatically emptied. aCompleted and aResult could be the same region.
|
|
|
|
void nsRegion::SubRect (const nsRectFast& aRect, nsRegion& aResult, nsRegion& aCompleted) const
|
|
{
|
|
nsRegion TmpRegion;
|
|
const nsRegion* pSrcRegion = this;
|
|
|
|
if (&aResult == this) // Copy region if it is both source and result
|
|
{
|
|
TmpRegion.Copy (*this);
|
|
pSrcRegion = &TmpRegion;
|
|
}
|
|
|
|
aResult.SetToElements (0);
|
|
|
|
const_cast<nsRegion*>(pSrcRegion)->mRectListHead.y = NS_COORD_GREATER_SENTINEL;
|
|
const RgnRect* pSrcRect = pSrcRegion->mRectListHead.next;
|
|
|
|
for ( ; pSrcRect->y < aRect.YMost () ; pSrcRect = pSrcRect->next)
|
|
{
|
|
nsRectFast TmpRect;
|
|
|
|
// If bottom of current rectangle is above the top of aRect then this rectangle
|
|
// could be moved to aCompleted region. Successive aRect rectangles from ordered
|
|
// list do not have to check this rectangle again.
|
|
if (pSrcRect->YMost () <= aRect.y)
|
|
{
|
|
aCompleted.InsertInPlace (new RgnRect (*pSrcRect));
|
|
continue;
|
|
}
|
|
|
|
if (!TmpRect.IntersectRect (*pSrcRect, aRect))
|
|
aResult.InsertInPlace (new RgnRect (*pSrcRect));
|
|
else
|
|
{
|
|
// Rectangle A. Subtract from this rectangle B
|
|
const nscoord ax = pSrcRect->x;
|
|
const nscoord axm = pSrcRect->XMost ();
|
|
const nscoord aw = pSrcRect->width;
|
|
const nscoord ay = pSrcRect->y;
|
|
const nscoord aym = pSrcRect->YMost ();
|
|
const nscoord ah = pSrcRect->height;
|
|
// Rectangle B. Subtract this from rectangle A
|
|
const nscoord bx = aRect.x;
|
|
const nscoord bxm = aRect.XMost ();
|
|
const nscoord by = aRect.y;
|
|
const nscoord bym = aRect.YMost ();
|
|
// Rectangle I. Area where rectangles A and B intersect
|
|
const nscoord ix = TmpRect.x;
|
|
const nscoord ixm = TmpRect.XMost ();
|
|
const nscoord iy = TmpRect.y;
|
|
const nscoord iym = TmpRect.YMost ();
|
|
const nscoord ih = TmpRect.height;
|
|
|
|
// There are 16 combinations how rectangles could intersect
|
|
|
|
if (bx <= ax && by <= ay)
|
|
{
|
|
if (bxm < axm && bym < aym) // 1.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ixm, ay, axm - ixm, ih));
|
|
aResult.InsertInPlace (new RgnRect (ax, iym, aw, aym - iym));
|
|
} else
|
|
if (bxm >= axm && bym < aym) // 2.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, iym, aw, aym - iym));
|
|
} else
|
|
if (bxm < axm && bym >= aym) // 3.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ixm, ay, axm - ixm, ah));
|
|
} else
|
|
if (pSrcRect->IsEqualInterior(aRect)) // 4. subset
|
|
{ // Current rectangle is equal to aRect
|
|
pSrcRect = pSrcRect->next; // don't add this one to the result, it's removed
|
|
break; // No any other rectangle in region can intersect it
|
|
}
|
|
} else
|
|
if (bx > ax && by <= ay)
|
|
{
|
|
if (bxm < axm && bym < aym) // 5.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, ix - ax, ih));
|
|
aResult.InsertInPlace (new RgnRect (ixm, ay, axm - ixm, ih));
|
|
aResult.InsertInPlace (new RgnRect (ax, iym, aw, aym - iym));
|
|
} else
|
|
if (bxm >= axm && bym < aym) // 6.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, ix - ax, ih));
|
|
aResult.InsertInPlace (new RgnRect (ax, iym, aw, aym - iym));
|
|
} else
|
|
if (bxm < axm && bym >= aym) // 7.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, ix - ax, ah));
|
|
aResult.InsertInPlace (new RgnRect (ixm, ay, axm - ixm, ah));
|
|
} else
|
|
if (bxm >= axm && bym >= aym) // 8.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, ix - ax, ah));
|
|
}
|
|
} else
|
|
if (bx <= ax && by > ay)
|
|
{
|
|
if (bxm < axm && bym < aym) // 9.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, aw, iy - ay));
|
|
aResult.InsertInPlace (new RgnRect (ixm, iy, axm - ixm, ih));
|
|
aResult.InsertInPlace (new RgnRect (ax, iym, aw, aym - iym));
|
|
} else
|
|
if (bxm >= axm && bym < aym) // 10.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, aw, iy - ay));
|
|
aResult.InsertInPlace (new RgnRect (ax, iym, aw, aym - iym));
|
|
} else
|
|
if (bxm < axm && bym >= aym) // 11.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, aw, iy - ay));
|
|
aResult.InsertInPlace (new RgnRect (ixm, iy, axm - ixm, ih));
|
|
} else
|
|
if (bxm >= axm && bym >= aym) // 12.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, aw, iy - ay));
|
|
}
|
|
} else
|
|
if (bx > ax && by > ay)
|
|
{
|
|
if (bxm < axm && bym < aym) // 13.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, aw, iy - ay));
|
|
aResult.InsertInPlace (new RgnRect (ax, iy, ix - ax, ih));
|
|
aResult.InsertInPlace (new RgnRect (ixm, iy, axm - ixm, ih));
|
|
aResult.InsertInPlace (new RgnRect (ax, iym, aw, aym - iym));
|
|
|
|
// Current rectangle fully overlays aRect. No any other rectangle can intersect it.
|
|
pSrcRect = pSrcRect->next; // don't add this one to the result, it's removed
|
|
break;
|
|
} else
|
|
if (bxm >= axm && bym < aym) // 14.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, aw, iy - ay));
|
|
aResult.InsertInPlace (new RgnRect (ax, iy, ix - ax, ih));
|
|
aResult.InsertInPlace (new RgnRect (ax, iym, aw, aym - iym));
|
|
} else
|
|
if (bxm < axm && bym >= aym) // 15.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, aw, iy - ay));
|
|
aResult.InsertInPlace (new RgnRect (ax, iy, ix - ax, ih));
|
|
aResult.InsertInPlace (new RgnRect (ixm, iy, axm - ixm, ih));
|
|
} else
|
|
if (bxm >= axm && bym >= aym) // 16.
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (ax, ay, aw, iy - ay));
|
|
aResult.InsertInPlace (new RgnRect (ax, iy, ix - ax, ih));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Just copy remaining rectangles in region which are below aRect and can't intersect it.
|
|
// If rectangles are in temporary region then they could be moved.
|
|
if (pSrcRegion == &TmpRegion)
|
|
TmpRegion.MoveInto (aResult, pSrcRect);
|
|
else
|
|
{
|
|
while (pSrcRect != &pSrcRegion->mRectListHead)
|
|
{
|
|
aResult.InsertInPlace (new RgnRect (*pSrcRect));
|
|
pSrcRect = pSrcRect->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool nsRegion::IsEqual (const nsRegion& aRegion) const
|
|
{
|
|
if (mRectCount == 0)
|
|
return (aRegion.mRectCount == 0) ? true : false;
|
|
|
|
if (aRegion.mRectCount == 0)
|
|
return (mRectCount == 0) ? true : false;
|
|
|
|
if (mRectCount == 1 && aRegion.mRectCount == 1) // Both regions are simple rectangles
|
|
return (mRectListHead.next->IsEqualInterior(*aRegion.mRectListHead.next));
|
|
else // At least one is complex region.
|
|
{
|
|
if (!mBoundRect.IsEqualInterior(aRegion.mBoundRect)) // If regions are equal then bounding rectangles should match
|
|
return false;
|
|
else
|
|
{
|
|
nsRegion TmpRegion;
|
|
TmpRegion.Xor (*this, aRegion); // Get difference between two regions
|
|
|
|
return (TmpRegion.mRectCount == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void nsRegion::MoveBy (nsPoint aPt)
|
|
{
|
|
if (aPt.x || aPt.y)
|
|
{
|
|
RgnRect* pRect = mRectListHead.next;
|
|
|
|
while (pRect != &mRectListHead)
|
|
{
|
|
pRect->MoveBy (aPt.x, aPt.y);
|
|
pRect = pRect->next;
|
|
}
|
|
|
|
mBoundRect.MoveBy (aPt.x, aPt.y);
|
|
}
|
|
}
|
|
|
|
nsRegion& nsRegion::ScaleRoundOut (float aXScale, float aYScale)
|
|
{
|
|
nsRegion region;
|
|
nsRegionRectIterator iter(*this);
|
|
for (;;) {
|
|
const nsRect* r = iter.Next();
|
|
if (!r)
|
|
break;
|
|
nsRect rect = *r;
|
|
rect.ScaleRoundOut(aXScale, aYScale);
|
|
region.Or(region, rect);
|
|
}
|
|
*this = region;
|
|
return *this;
|
|
}
|
|
|
|
nsRegion& nsRegion::ScaleInverseRoundOut (float aXScale, float aYScale)
|
|
{
|
|
nsRegion region;
|
|
nsRegionRectIterator iter(*this);
|
|
for (;;) {
|
|
const nsRect* r = iter.Next();
|
|
if (!r)
|
|
break;
|
|
nsRect rect = *r;
|
|
rect.ScaleInverseRoundOut(aXScale, aYScale);
|
|
region.Or(region, rect);
|
|
}
|
|
*this = region;
|
|
return *this;
|
|
}
|
|
|
|
nsRegion nsRegion::ConvertAppUnitsRoundOut (int32_t aFromAPP, int32_t aToAPP) const
|
|
{
|
|
if (aFromAPP == aToAPP) {
|
|
return *this;
|
|
}
|
|
// Do it in a simplistic and slow way to avoid any weird behaviour with
|
|
// rounding causing rects to overlap. Should be fast enough for what we need.
|
|
nsRegion region;
|
|
nsRegionRectIterator iter(*this);
|
|
for (;;) {
|
|
const nsRect* r = iter.Next();
|
|
if (!r)
|
|
break;
|
|
nsRect rect = r->ConvertAppUnitsRoundOut(aFromAPP, aToAPP);
|
|
region.Or(region, rect);
|
|
}
|
|
return region;
|
|
}
|
|
|
|
nsRegion nsRegion::ConvertAppUnitsRoundIn (int32_t aFromAPP, int32_t aToAPP) const
|
|
{
|
|
if (aFromAPP == aToAPP) {
|
|
return *this;
|
|
}
|
|
// Do it in a simplistic and slow way to avoid any weird behaviour with
|
|
// rounding causing rects to overlap. Should be fast enough for what we need.
|
|
nsRegion region;
|
|
nsRegionRectIterator iter(*this);
|
|
for (;;) {
|
|
const nsRect* r = iter.Next();
|
|
if (!r)
|
|
break;
|
|
nsRect rect = r->ConvertAppUnitsRoundIn(aFromAPP, aToAPP);
|
|
region.Or(region, rect);
|
|
}
|
|
return region;
|
|
}
|
|
|
|
nsIntRegion nsRegion::ToPixels (nscoord aAppUnitsPerPixel, bool aOutsidePixels) const
|
|
{
|
|
nsIntRegion result;
|
|
nsRegionRectIterator rgnIter(*this);
|
|
const nsRect* currentRect;
|
|
while ((currentRect = rgnIter.Next())) {
|
|
nsIntRect deviceRect;
|
|
if (aOutsidePixels)
|
|
deviceRect = currentRect->ToOutsidePixels(aAppUnitsPerPixel);
|
|
else
|
|
deviceRect = currentRect->ToNearestPixels(aAppUnitsPerPixel);
|
|
result.Or(result, deviceRect);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
nsIntRegion nsRegion::ToOutsidePixels (nscoord aAppUnitsPerPixel) const
|
|
{
|
|
return ToPixels(aAppUnitsPerPixel, true);
|
|
}
|
|
|
|
nsIntRegion nsRegion::ToNearestPixels (nscoord aAppUnitsPerPixel) const
|
|
{
|
|
return ToPixels(aAppUnitsPerPixel, false);
|
|
}
|
|
|
|
nsIntRegion nsRegion::ScaleToNearestPixels (float aScaleX, float aScaleY,
|
|
nscoord aAppUnitsPerPixel) const
|
|
{
|
|
nsIntRegion result;
|
|
nsRegionRectIterator rgnIter(*this);
|
|
const nsRect* currentRect;
|
|
while ((currentRect = rgnIter.Next())) {
|
|
nsIntRect deviceRect =
|
|
currentRect->ScaleToNearestPixels(aScaleX, aScaleY, aAppUnitsPerPixel);
|
|
result.Or(result, deviceRect);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
nsIntRegion nsRegion::ScaleToOutsidePixels (float aScaleX, float aScaleY,
|
|
nscoord aAppUnitsPerPixel) const
|
|
{
|
|
nsIntRegion result;
|
|
nsRegionRectIterator rgnIter(*this);
|
|
const nsRect* currentRect;
|
|
while ((currentRect = rgnIter.Next())) {
|
|
nsIntRect deviceRect =
|
|
currentRect->ScaleToOutsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel);
|
|
result.Or(result, deviceRect);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
nsIntRegion nsRegion::ScaleToInsidePixels (float aScaleX, float aScaleY,
|
|
nscoord aAppUnitsPerPixel) const
|
|
{
|
|
/* When scaling a rect, walk forward through the rect list up until the y value is greater
|
|
* than the current rect's YMost() value.
|
|
*
|
|
* For each rect found, check if the rects have a touching edge (in unscaled coordinates),
|
|
* and if one edge is entirely contained within the other.
|
|
*
|
|
* If it is, then the contained edge can be moved (in scaled pixels) to ensure that no
|
|
* gap exists.
|
|
*
|
|
* Since this could be potentially expensive - O(n^2), we only attempt this algorithm
|
|
* for the first rect.
|
|
*/
|
|
|
|
nsIntRegion result;
|
|
RgnRect* pRect = mRectListHead.next;
|
|
RgnRect* first = pRect;
|
|
|
|
nsIntRect firstDeviceRect;
|
|
if (pRect != &mRectListHead) {
|
|
firstDeviceRect =
|
|
pRect->ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel);
|
|
pRect = pRect->next;
|
|
}
|
|
|
|
while (pRect != &mRectListHead)
|
|
{
|
|
nsIntRect deviceRect =
|
|
pRect->ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel);
|
|
|
|
if (pRect->y <= first->YMost()) {
|
|
if (pRect->XMost() == first->x && pRect->YMost() <= first->YMost()) {
|
|
// pRect is touching on the left edge of the first rect and contained within
|
|
// the length of its left edge
|
|
deviceRect.SetRightEdge(firstDeviceRect.x);
|
|
} else if (pRect->x == first->XMost() && pRect->YMost() <= first->YMost()) {
|
|
// pRect is touching on the right edge of the first rect and contained within
|
|
// the length of its right edge
|
|
deviceRect.SetLeftEdge(firstDeviceRect.XMost());
|
|
} else if (pRect->y == first->YMost()) {
|
|
// The bottom of the first rect is on the same line as the top of pRect, but
|
|
// they aren't necessarily contained.
|
|
if (pRect->x <= first->x && pRect->XMost() >= first->XMost()) {
|
|
// The top of pRect contains the bottom of the first rect
|
|
firstDeviceRect.SetBottomEdge(deviceRect.y);
|
|
} else if (pRect->x >= first->x && pRect->XMost() <= first->XMost()) {
|
|
// The bottom of the first contains the top of pRect
|
|
deviceRect.SetTopEdge(firstDeviceRect.YMost());
|
|
}
|
|
}
|
|
}
|
|
pRect = pRect->next;
|
|
result.Or(result, deviceRect);
|
|
}
|
|
|
|
result.Or(result, firstDeviceRect);
|
|
return result;
|
|
}
|
|
|
|
// A cell's "value" is a pair consisting of
|
|
// a) the area of the subrectangle it corresponds to, if it's in
|
|
// aContainingRect and in the region, 0 otherwise
|
|
// b) the area of the subrectangle it corresponds to, if it's in the region,
|
|
// 0 otherwise
|
|
// Addition, subtraction and identity are defined on these values in the
|
|
// obvious way. Partial order is lexicographic.
|
|
// A "large negative value" is defined with large negative numbers for both
|
|
// fields of the pair. This negative value has the property that adding any
|
|
// number of non-negative values to it always results in a negative value.
|
|
//
|
|
// The GetLargestRectangle algorithm works in three phases:
|
|
// 1) Convert the region into a grid by adding vertical/horizontal lines for
|
|
// each edge of each rectangle in the region.
|
|
// 2) For each rectangle in the region, for each cell it contains, set that
|
|
// cells's value as described above.
|
|
// 3) Calculate the submatrix with the largest sum such that none of its cells
|
|
// contain any 0s (empty regions). The rectangle represented by the
|
|
// submatrix is the largest rectangle in the region.
|
|
//
|
|
// Let k be the number of rectangles in the region.
|
|
// Let m be the height of the grid generated in step 1.
|
|
// Let n be the width of the grid generated in step 1.
|
|
//
|
|
// Step 1 is O(k) in time and O(m+n) in space for the sparse grid.
|
|
// Step 2 is O(mn) in time and O(mn) in additional space for the full grid.
|
|
// Step 3 is O(m^2 n) in time and O(mn) in additional space
|
|
//
|
|
// The implementation of steps 1 and 2 are rather straightforward. However our
|
|
// implementation of step 3 uses dynamic programming to achieve its efficiency.
|
|
//
|
|
// Psuedo code for step 3 is as follows where G is the grid from step 1 and A
|
|
// is the array from step 2:
|
|
// Phase3 = function (G, A, m, n) {
|
|
// let (t,b,l,r,_) = MaxSum2D(A,m,n)
|
|
// return rect(G[t],G[l],G[r],G[b]);
|
|
// }
|
|
// MaxSum2D = function (A, m, n) {
|
|
// S = array(m+1,n+1)
|
|
// S[0][i] = 0 for i in [0,n]
|
|
// S[j][0] = 0 for j in [0,m]
|
|
// S[j][i] = (if A[j-1][i-1] = 0 then some large negative value else A[j-1][i-1])
|
|
// + S[j-1][n] + S[j][i-1] - S[j-1][i-1]
|
|
//
|
|
// // top, bottom, left, right, area
|
|
// var maxRect = (-1, -1, -1, -1, 0);
|
|
//
|
|
// for all (m',m'') in [0, m]^2 {
|
|
// let B = { S[m'][i] - S[m''][i] | 0 <= i <= n }
|
|
// let ((l,r),area) = MaxSum1D(B,n+1)
|
|
// if (area > maxRect.area) {
|
|
// maxRect := (m', m'', l, r, area)
|
|
// }
|
|
// }
|
|
//
|
|
// return maxRect;
|
|
// }
|
|
//
|
|
// Originally taken from Improved algorithms for the k-maximum subarray problem
|
|
// for small k - SE Bae, T Takaoka but modified to show the explicit tracking
|
|
// of indices and we already have the prefix sums from our one call site so
|
|
// there's no need to construct them.
|
|
// MaxSum1D = function (A,n) {
|
|
// var minIdx = 0;
|
|
// var min = 0;
|
|
// var maxIndices = (0,0);
|
|
// var max = 0;
|
|
// for i in range(n) {
|
|
// let cand = A[i] - min;
|
|
// if (cand > max) {
|
|
// max := cand;
|
|
// maxIndices := (minIdx, i)
|
|
// }
|
|
// if (min > A[i]) {
|
|
// min := A[i];
|
|
// minIdx := i;
|
|
// }
|
|
// }
|
|
// return (minIdx, maxIdx, max);
|
|
// }
|
|
|
|
namespace {
|
|
// This class represents a partitioning of an axis delineated by coordinates.
|
|
// It internally maintains a sorted array of coordinates.
|
|
class AxisPartition {
|
|
public:
|
|
// Adds a new partition at the given coordinate to this partitioning. If
|
|
// the coordinate is already present in the partitioning, this does nothing.
|
|
void InsertCoord(nscoord c) {
|
|
uint32_t i;
|
|
if (!mStops.GreatestIndexLtEq(c, i)) {
|
|
mStops.InsertElementAt(i, c);
|
|
}
|
|
}
|
|
|
|
// Returns the array index of the given partition point. The partition
|
|
// point must already be present in the partitioning.
|
|
int32_t IndexOf(nscoord p) const {
|
|
return mStops.BinaryIndexOf(p);
|
|
}
|
|
|
|
// Returns the partition at the given index which must be non-zero and
|
|
// less than the number of partitions in this partitioning.
|
|
nscoord StopAt(int32_t index) const {
|
|
return mStops[index];
|
|
}
|
|
|
|
// Returns the size of the gap between the partition at the given index and
|
|
// the next partition in this partitioning. If the index is the last index
|
|
// in the partitioning, the result is undefined.
|
|
nscoord StopSize(int32_t index) const {
|
|
return mStops[index+1] - mStops[index];
|
|
}
|
|
|
|
// Returns the number of partitions in this partitioning.
|
|
int32_t GetNumStops() const { return mStops.Length(); }
|
|
|
|
private:
|
|
nsTArray<nscoord> mStops;
|
|
};
|
|
|
|
const int64_t kVeryLargeNegativeNumber = 0xffff000000000000ll;
|
|
|
|
struct SizePair {
|
|
int64_t mSizeContainingRect;
|
|
int64_t mSize;
|
|
|
|
SizePair() : mSizeContainingRect(0), mSize(0) {}
|
|
|
|
static SizePair VeryLargeNegative() {
|
|
SizePair result;
|
|
result.mSize = result.mSizeContainingRect = kVeryLargeNegativeNumber;
|
|
return result;
|
|
}
|
|
SizePair& operator=(const SizePair& aOther) {
|
|
mSizeContainingRect = aOther.mSizeContainingRect;
|
|
mSize = aOther.mSize;
|
|
return *this;
|
|
}
|
|
bool operator<(const SizePair& aOther) const {
|
|
if (mSizeContainingRect < aOther.mSizeContainingRect)
|
|
return true;
|
|
if (mSizeContainingRect > aOther.mSizeContainingRect)
|
|
return false;
|
|
return mSize < aOther.mSize;
|
|
}
|
|
bool operator>(const SizePair& aOther) const {
|
|
return aOther.operator<(*this);
|
|
}
|
|
SizePair operator+(const SizePair& aOther) const {
|
|
SizePair result = *this;
|
|
result.mSizeContainingRect += aOther.mSizeContainingRect;
|
|
result.mSize += aOther.mSize;
|
|
return result;
|
|
}
|
|
SizePair operator-(const SizePair& aOther) const {
|
|
SizePair result = *this;
|
|
result.mSizeContainingRect -= aOther.mSizeContainingRect;
|
|
result.mSize -= aOther.mSize;
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// Returns the sum and indices of the subarray with the maximum sum of the
|
|
// given array (A,n), assuming the array is already in prefix sum form.
|
|
SizePair MaxSum1D(const nsTArray<SizePair> &A, int32_t n,
|
|
int32_t *minIdx, int32_t *maxIdx) {
|
|
// The min/max indicies of the largest subarray found so far
|
|
SizePair min, max;
|
|
int32_t currentMinIdx = 0;
|
|
|
|
*minIdx = 0;
|
|
*maxIdx = 0;
|
|
|
|
// Because we're given the array in prefix sum form, we know the first
|
|
// element is 0
|
|
for(int32_t i = 1; i < n; i++) {
|
|
SizePair cand = A[i] - min;
|
|
if (cand > max) {
|
|
max = cand;
|
|
*minIdx = currentMinIdx;
|
|
*maxIdx = i;
|
|
}
|
|
if (min > A[i]) {
|
|
min = A[i];
|
|
currentMinIdx = i;
|
|
}
|
|
}
|
|
|
|
return max;
|
|
}
|
|
}
|
|
|
|
nsRect nsRegion::GetLargestRectangle (const nsRect& aContainingRect) const {
|
|
nsRect bestRect;
|
|
|
|
if (mRectCount <= 1) {
|
|
bestRect = mBoundRect;
|
|
return bestRect;
|
|
}
|
|
|
|
AxisPartition xaxis, yaxis;
|
|
|
|
// Step 1: Calculate the grid lines
|
|
nsRegionRectIterator iter(*this);
|
|
const nsRect *currentRect;
|
|
while ((currentRect = iter.Next())) {
|
|
xaxis.InsertCoord(currentRect->x);
|
|
xaxis.InsertCoord(currentRect->XMost());
|
|
yaxis.InsertCoord(currentRect->y);
|
|
yaxis.InsertCoord(currentRect->YMost());
|
|
}
|
|
if (!aContainingRect.IsEmpty()) {
|
|
xaxis.InsertCoord(aContainingRect.x);
|
|
xaxis.InsertCoord(aContainingRect.XMost());
|
|
yaxis.InsertCoord(aContainingRect.y);
|
|
yaxis.InsertCoord(aContainingRect.YMost());
|
|
}
|
|
|
|
// Step 2: Fill out the grid with the areas
|
|
// Note: due to the ordering of rectangles in the region, it is not always
|
|
// possible to combine steps 2 and 3 so we don't try to be clever.
|
|
int32_t matrixHeight = yaxis.GetNumStops() - 1;
|
|
int32_t matrixWidth = xaxis.GetNumStops() - 1;
|
|
int32_t matrixSize = matrixHeight * matrixWidth;
|
|
nsTArray<SizePair> areas(matrixSize);
|
|
areas.SetLength(matrixSize);
|
|
|
|
iter.Reset();
|
|
while ((currentRect = iter.Next())) {
|
|
int32_t xstart = xaxis.IndexOf(currentRect->x);
|
|
int32_t xend = xaxis.IndexOf(currentRect->XMost());
|
|
int32_t y = yaxis.IndexOf(currentRect->y);
|
|
int32_t yend = yaxis.IndexOf(currentRect->YMost());
|
|
|
|
for (; y < yend; y++) {
|
|
nscoord height = yaxis.StopSize(y);
|
|
for (int32_t x = xstart; x < xend; x++) {
|
|
nscoord width = xaxis.StopSize(x);
|
|
int64_t size = width*int64_t(height);
|
|
if (currentRect->Intersects(aContainingRect)) {
|
|
areas[y*matrixWidth+x].mSizeContainingRect = size;
|
|
}
|
|
areas[y*matrixWidth+x].mSize = size;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step 3: Find the maximum submatrix sum that does not contain a rectangle
|
|
{
|
|
// First get the prefix sum array
|
|
int32_t m = matrixHeight + 1;
|
|
int32_t n = matrixWidth + 1;
|
|
nsTArray<SizePair> pareas(m*n);
|
|
pareas.SetLength(m*n);
|
|
for (int32_t y = 1; y < m; y++) {
|
|
for (int32_t x = 1; x < n; x++) {
|
|
SizePair area = areas[(y-1)*matrixWidth+x-1];
|
|
if (!area.mSize) {
|
|
area = SizePair::VeryLargeNegative();
|
|
}
|
|
area = area + pareas[ y*n+x-1]
|
|
+ pareas[(y-1)*n+x ]
|
|
- pareas[(y-1)*n+x-1];
|
|
pareas[y*n+x] = area;
|
|
}
|
|
}
|
|
|
|
// No longer need the grid
|
|
areas.SetLength(0);
|
|
|
|
SizePair bestArea;
|
|
struct {
|
|
int32_t left, top, right, bottom;
|
|
} bestRectIndices = { 0, 0, 0, 0 };
|
|
for (int32_t m1 = 0; m1 < m; m1++) {
|
|
for (int32_t m2 = m1+1; m2 < m; m2++) {
|
|
nsTArray<SizePair> B;
|
|
B.SetLength(n);
|
|
for (int32_t i = 0; i < n; i++) {
|
|
B[i] = pareas[m2*n+i] - pareas[m1*n+i];
|
|
}
|
|
int32_t minIdx, maxIdx;
|
|
SizePair area = MaxSum1D(B, n, &minIdx, &maxIdx);
|
|
if (area > bestArea) {
|
|
bestRectIndices.left = minIdx;
|
|
bestRectIndices.top = m1;
|
|
bestRectIndices.right = maxIdx;
|
|
bestRectIndices.bottom = m2;
|
|
bestArea = area;
|
|
}
|
|
}
|
|
}
|
|
|
|
bestRect.MoveTo(xaxis.StopAt(bestRectIndices.left),
|
|
yaxis.StopAt(bestRectIndices.top));
|
|
bestRect.SizeTo(xaxis.StopAt(bestRectIndices.right) - bestRect.x,
|
|
yaxis.StopAt(bestRectIndices.bottom) - bestRect.y);
|
|
}
|
|
|
|
return bestRect;
|
|
}
|
|
|
|
void nsRegion::SimplifyOutward (uint32_t aMaxRects)
|
|
{
|
|
NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count");
|
|
|
|
if (mRectCount <= aMaxRects)
|
|
return;
|
|
|
|
// Try combining rects in horizontal bands into a single rect
|
|
RgnRect* pRect = mRectListHead.next;
|
|
while (pRect != &mRectListHead)
|
|
{
|
|
// Combine with the following rectangle if they have the same YMost
|
|
// or if they overlap vertically. This ensures that all overlapping
|
|
// rectangles are merged, preserving the invariant that rectangles
|
|
// don't overlap.
|
|
// The goal here is to try to keep groups of rectangles that are vertically
|
|
// discontiguous as separate rectangles in the final region. This is
|
|
// simple and fast to implement and page contents tend to vary more
|
|
// vertically than horizontally (which is why our rectangles are stored
|
|
// sorted by y-coordinate, too).
|
|
while (pRect->next != &mRectListHead &&
|
|
pRect->YMost () >= pRect->next->y)
|
|
{
|
|
pRect->UnionRect(*pRect, *pRect->next);
|
|
delete Remove (pRect->next);
|
|
}
|
|
|
|
pRect = pRect->next;
|
|
}
|
|
|
|
if (mRectCount <= aMaxRects)
|
|
return;
|
|
|
|
*this = GetBounds();
|
|
}
|
|
|
|
void nsRegion::SimplifyInward (uint32_t aMaxRects)
|
|
{
|
|
NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count");
|
|
|
|
if (mRectCount <= aMaxRects)
|
|
return;
|
|
|
|
SetEmpty();
|
|
}
|
|
|
|
void nsRegion::SimpleSubtract (const nsRect& aRect)
|
|
{
|
|
if (aRect.IsEmpty())
|
|
return;
|
|
|
|
// protect against aRect being one of our own rectangles
|
|
nsRect param = aRect;
|
|
RgnRect* r = mRectListHead.next;
|
|
while (r != &mRectListHead)
|
|
{
|
|
RgnRect* next = r->next;
|
|
if (param.Contains(*r)) {
|
|
delete Remove(r);
|
|
}
|
|
r = next;
|
|
}
|
|
|
|
Optimize();
|
|
}
|
|
|
|
void nsRegion::SimpleSubtract (const nsRegion& aRegion)
|
|
{
|
|
if (aRegion.IsEmpty())
|
|
return;
|
|
|
|
if (&aRegion == this) {
|
|
SetEmpty();
|
|
return;
|
|
}
|
|
|
|
const RgnRect* r = aRegion.mRectListHead.next;
|
|
while (r != &aRegion.mRectListHead)
|
|
{
|
|
SimpleSubtract(*r);
|
|
r = r->next;
|
|
}
|
|
|
|
Optimize();
|
|
}
|
|
|
|
nsCString
|
|
nsRegion::ToString()
|
|
{
|
|
nsCString result;
|
|
result.AppendLiteral("[");
|
|
const RgnRect* r = mRectListHead.next;
|
|
while (r != &mRectListHead)
|
|
{
|
|
if (r != mRectListHead.next) {
|
|
result.AppendLiteral("; ");
|
|
}
|
|
result.Append(nsPrintfCString("%d,%d,%d,%d", r->x, r->y, r->XMost(), r->YMost()));
|
|
r = r->next;
|
|
}
|
|
result.AppendLiteral("]");
|
|
|
|
return result;
|
|
}
|
|
|
|
nsRegion nsIntRegion::ToAppUnits (nscoord aAppUnitsPerPixel) const
|
|
{
|
|
nsRegion result;
|
|
nsIntRegionRectIterator rgnIter(*this);
|
|
const nsIntRect* currentRect;
|
|
while ((currentRect = rgnIter.Next())) {
|
|
nsRect appRect = currentRect->ToAppUnits(aAppUnitsPerPixel);
|
|
result.Or(result, appRect);
|
|
}
|
|
return result;
|
|
}
|