michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: // vim:cindent:ts=2:et:sw=2: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* class that manages rules for positioning floats */ michael@0: michael@0: #ifndef nsFloatManager_h_ michael@0: #define nsFloatManager_h_ michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: #include "nsIntervalSet.h" michael@0: #include "nsCoord.h" michael@0: #include "nsRect.h" michael@0: #include "nsTArray.h" michael@0: #include "nsFrameList.h" // for DEBUG_FRAME_DUMP michael@0: michael@0: class nsIPresShell; michael@0: class nsIFrame; michael@0: struct nsHTMLReflowState; michael@0: class nsPresContext; michael@0: michael@0: /** michael@0: * The available space for content not occupied by floats is divided michael@0: * into a (vertical) sequence of rectangles. However, we need to know michael@0: * not only the rectangle, but also whether it was reduced (from the michael@0: * content rectangle) by floats that actually intruded into the content michael@0: * rectangle. michael@0: */ michael@0: struct nsFlowAreaRect { michael@0: nsRect mRect; michael@0: bool mHasFloats; michael@0: michael@0: nsFlowAreaRect(nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight, michael@0: bool aHasFloats) michael@0: : mRect(aX, aY, aWidth, aHeight), mHasFloats(aHasFloats) {} michael@0: }; michael@0: michael@0: #define NS_FLOAT_MANAGER_CACHE_SIZE 4 michael@0: michael@0: class nsFloatManager { michael@0: public: michael@0: nsFloatManager(nsIPresShell* aPresShell); michael@0: ~nsFloatManager(); michael@0: michael@0: void* operator new(size_t aSize) CPP_THROW_NEW; michael@0: void operator delete(void* aPtr, size_t aSize); michael@0: michael@0: static void Shutdown(); michael@0: michael@0: /** michael@0: * Get float region stored on the frame. (Defaults to mRect if it's michael@0: * not there.) The float region is the area impacted by this float; michael@0: * the coordinates are relative to the containing block frame. michael@0: */ michael@0: static nsRect GetRegionFor(nsIFrame* aFloatFrame); michael@0: /** michael@0: * Calculate the float region for this frame using aMargin and the michael@0: * frame's mRect. The region includes the margins around the float, michael@0: * but doesn't include the relative offsets. michael@0: * Note that if the frame is or has a continuation, aMargin's top michael@0: * and/or bottom must be zeroed by the caller. michael@0: */ michael@0: static nsRect CalculateRegionFor(nsIFrame* aFloatFrame, michael@0: const nsMargin& aMargin); michael@0: /** michael@0: * Store the float region on the frame. The region is stored michael@0: * as a delta against the mRect, so repositioning the frame will michael@0: * also reposition the float region. michael@0: */ michael@0: static void StoreRegionFor(nsIFrame* aFloat, nsRect& aRegion); michael@0: michael@0: // Structure that stores the current state of a frame manager for michael@0: // Save/Restore purposes. michael@0: struct SavedState; michael@0: friend struct SavedState; michael@0: struct SavedState { michael@0: private: michael@0: uint32_t mFloatInfoCount; michael@0: nscoord mX, mY; michael@0: bool mPushedLeftFloatPastBreak; michael@0: bool mPushedRightFloatPastBreak; michael@0: bool mSplitLeftFloatAcrossBreak; michael@0: bool mSplitRightFloatAcrossBreak; michael@0: michael@0: friend class nsFloatManager; michael@0: }; michael@0: michael@0: /** michael@0: * Translate the current origin by the specified (dx, dy). This michael@0: * creates a new local coordinate space relative to the current michael@0: * coordinate space. michael@0: */ michael@0: void Translate(nscoord aDx, nscoord aDy) { mX += aDx; mY += aDy; } michael@0: michael@0: /** michael@0: * Returns the current translation from local coordinate space to michael@0: * world coordinate space. This represents the accumulated calls to michael@0: * Translate(). michael@0: */ michael@0: void GetTranslation(nscoord& aX, nscoord& aY) const { aX = mX; aY = mY; } michael@0: michael@0: /** michael@0: * Get information about the area available to content that flows michael@0: * around floats. Two different types of space can be requested: michael@0: * BAND_FROM_POINT: returns the band containing vertical coordinate michael@0: * |aY| (though actually with the top truncated to begin at aY), michael@0: * but up to at most |aHeight| (which may be nscoord_MAX). michael@0: * This will return the tallest rectangle whose top is |aY| and in michael@0: * which there are no changes in what floats are on the sides of michael@0: * that rectangle, but will limit the height of the rectangle to michael@0: * |aHeight|. The left and right edges of the rectangle give the michael@0: * area available for line boxes in that space. The width of this michael@0: * resulting rectangle will not be negative. michael@0: * WIDTH_WITHIN_HEIGHT: This returns a rectangle whose top is aY and michael@0: * whose height is exactly aHeight. Its left and right edges give michael@0: * the left and right edges of the space that can be used for line michael@0: * boxes *throughout* that space. (It is possible that more michael@0: * horizontal space could be used in part of the space if a float michael@0: * begins or ends in it.) The width of the resulting rectangle michael@0: * can be negative. michael@0: * michael@0: * @param aY [in] vertical coordinate for top of available space michael@0: * desired michael@0: * @param aHeight [in] see above michael@0: * @param aContentArea [in] an nsRect representing the content area michael@0: * @param aState [in] If null, use the current state, otherwise, do michael@0: * computation based only on floats present in the given michael@0: * saved state. michael@0: * @return An nsFlowAreaRect whose: michael@0: * mRect is the resulting rectangle for line boxes. It will not michael@0: * extend beyond aContentArea's horizontal bounds, but may be michael@0: * narrower when floats are present. michael@0: * mBandHasFloats is whether there are floats at the sides of the michael@0: * return value including those that do not reduce the line box michael@0: * width at all (because they are entirely in the margins) michael@0: * michael@0: * aY and aAvailSpace are positioned relative to the current translation michael@0: */ michael@0: enum BandInfoType { BAND_FROM_POINT, WIDTH_WITHIN_HEIGHT }; michael@0: nsFlowAreaRect GetFlowArea(nscoord aY, BandInfoType aInfoType, michael@0: nscoord aHeight, nsRect aContentArea, michael@0: SavedState* aState) const; michael@0: michael@0: /** michael@0: * Add a float that comes after all floats previously added. Its top michael@0: * must be even with or below the top of all previous floats. michael@0: * michael@0: * aMarginRect is relative to the current translation. The caller michael@0: * must ensure aMarginRect.height >= 0 and aMarginRect.width >= 0. michael@0: */ michael@0: nsresult AddFloat(nsIFrame* aFloatFrame, const nsRect& aMarginRect); michael@0: michael@0: /** michael@0: * Notify that we tried to place a float that could not fit at all and michael@0: * had to be pushed to the next page/column? (If so, we can't place michael@0: * any more floats in this page/column because of the rule that the michael@0: * top of a float cannot be above the top of an earlier float. It michael@0: * also means that any clear needs to continue to the next column.) michael@0: */ michael@0: void SetPushedLeftFloatPastBreak() michael@0: { mPushedLeftFloatPastBreak = true; } michael@0: void SetPushedRightFloatPastBreak() michael@0: { mPushedRightFloatPastBreak = true; } michael@0: michael@0: /** michael@0: * Notify that we split a float, with part of it needing to be pushed michael@0: * to the next page/column. (This means that any 'clear' needs to michael@0: * continue to the next page/column.) michael@0: */ michael@0: void SetSplitLeftFloatAcrossBreak() michael@0: { mSplitLeftFloatAcrossBreak = true; } michael@0: void SetSplitRightFloatAcrossBreak() michael@0: { mSplitRightFloatAcrossBreak = true; } michael@0: michael@0: /** michael@0: * Remove the regions associated with this floating frame and its michael@0: * next-sibling list. Some of the frames may never have been added; michael@0: * we just skip those. This is not fully general; it only works as michael@0: * long as the N frames to be removed are the last N frames to have michael@0: * been added; if there's a frame in the middle of them that should michael@0: * not be removed, YOU LOSE. michael@0: */ michael@0: nsresult RemoveTrailingRegions(nsIFrame* aFrameList); michael@0: michael@0: private: michael@0: struct FloatInfo; michael@0: public: michael@0: michael@0: bool HasAnyFloats() const { return !mFloats.IsEmpty(); } michael@0: michael@0: /** michael@0: * Methods for dealing with the propagation of float damage during michael@0: * reflow. michael@0: */ michael@0: bool HasFloatDamage() const michael@0: { michael@0: return !mFloatDamage.IsEmpty(); michael@0: } michael@0: michael@0: void IncludeInDamage(nscoord aIntervalBegin, nscoord aIntervalEnd) michael@0: { michael@0: mFloatDamage.IncludeInterval(aIntervalBegin + mY, aIntervalEnd + mY); michael@0: } michael@0: michael@0: bool IntersectsDamage(nscoord aIntervalBegin, nscoord aIntervalEnd) const michael@0: { michael@0: return mFloatDamage.Intersects(aIntervalBegin + mY, aIntervalEnd + mY); michael@0: } michael@0: michael@0: /** michael@0: * Saves the current state of the float manager into aState. michael@0: */ michael@0: void PushState(SavedState* aState); michael@0: michael@0: /** michael@0: * Restores the float manager to the saved state. michael@0: * michael@0: * These states must be managed using stack discipline. PopState can only michael@0: * be used after PushState has been used to save the state, and it can only michael@0: * be used once --- although it can be omitted; saved states can be ignored. michael@0: * States must be popped in the reverse order they were pushed. A michael@0: * call to PopState invalidates any saved states Pushed after the michael@0: * state passed to PopState was pushed. michael@0: */ michael@0: void PopState(SavedState* aState); michael@0: michael@0: /** michael@0: * Get the top of the last float placed into the float manager, to michael@0: * enforce the rule that a float can't be above an earlier float. michael@0: * Returns the minimum nscoord value if there are no floats. michael@0: * michael@0: * The result is relative to the current translation. michael@0: */ michael@0: nscoord GetLowestFloatTop() const; michael@0: michael@0: /** michael@0: * Return the coordinate of the lowest float matching aBreakType in this michael@0: * float manager. Returns aY if there are no matching floats. michael@0: * michael@0: * Both aY and the result are relative to the current translation. michael@0: */ michael@0: enum { michael@0: // Tell ClearFloats not to push to nscoord_MAX when floats have been michael@0: // pushed to the next page/column. michael@0: DONT_CLEAR_PUSHED_FLOATS = (1<<0) michael@0: }; michael@0: nscoord ClearFloats(nscoord aY, uint8_t aBreakType, uint32_t aFlags = 0) const; michael@0: michael@0: /** michael@0: * Checks if clear would pass into the floats' BFC's next-in-flow, michael@0: * i.e. whether floats affecting this clear have continuations. michael@0: */ michael@0: bool ClearContinues(uint8_t aBreakType) const; michael@0: michael@0: void AssertStateMatches(SavedState *aState) const michael@0: { michael@0: NS_ASSERTION(aState->mX == mX && aState->mY == mY && michael@0: aState->mPushedLeftFloatPastBreak == michael@0: mPushedLeftFloatPastBreak && michael@0: aState->mPushedRightFloatPastBreak == michael@0: mPushedRightFloatPastBreak && michael@0: aState->mSplitLeftFloatAcrossBreak == michael@0: mSplitLeftFloatAcrossBreak && michael@0: aState->mSplitRightFloatAcrossBreak == michael@0: mSplitRightFloatAcrossBreak && michael@0: aState->mFloatInfoCount == mFloats.Length(), michael@0: "float manager state should match saved state"); michael@0: } michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: /** michael@0: * Dump the state of the float manager out to a file. michael@0: */ michael@0: nsresult List(FILE* out) const; michael@0: #endif michael@0: michael@0: private: michael@0: michael@0: struct FloatInfo { michael@0: nsIFrame *const mFrame; michael@0: nsRect mRect; michael@0: // The lowest bottoms of left/right floats up to and including this one. michael@0: nscoord mLeftYMost, mRightYMost; michael@0: michael@0: FloatInfo(nsIFrame* aFrame, const nsRect& aRect); michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: FloatInfo(const FloatInfo& aOther); michael@0: ~FloatInfo(); michael@0: #endif michael@0: }; michael@0: michael@0: nscoord mX, mY; // translation from local to global coordinate space michael@0: nsTArray mFloats; michael@0: nsIntervalSet mFloatDamage; michael@0: michael@0: // Did we try to place a float that could not fit at all and had to be michael@0: // pushed to the next page/column? If so, we can't place any more michael@0: // floats in this page/column because of the rule that the top of a michael@0: // float cannot be above the top of an earlier float. And we also michael@0: // need to apply this information to 'clear', and thus need to michael@0: // separate left and right floats. michael@0: bool mPushedLeftFloatPastBreak; michael@0: bool mPushedRightFloatPastBreak; michael@0: michael@0: // Did we split a float, with part of it needing to be pushed to the michael@0: // next page/column. This means that any 'clear' needs to continue to michael@0: // the next page/column. michael@0: bool mSplitLeftFloatAcrossBreak; michael@0: bool mSplitRightFloatAcrossBreak; michael@0: michael@0: static int32_t sCachedFloatManagerCount; michael@0: static void* sCachedFloatManagers[NS_FLOAT_MANAGER_CACHE_SIZE]; michael@0: michael@0: nsFloatManager(const nsFloatManager&) MOZ_DELETE; michael@0: void operator=(const nsFloatManager&) MOZ_DELETE; michael@0: }; michael@0: michael@0: /** michael@0: * A helper class to manage maintenance of the float manager during michael@0: * nsBlockFrame::Reflow. It automatically restores the old float michael@0: * manager in the reflow state when the object goes out of scope. michael@0: */ michael@0: class nsAutoFloatManager { michael@0: public: michael@0: nsAutoFloatManager(nsHTMLReflowState& aReflowState) michael@0: : mReflowState(aReflowState), michael@0: mNew(nullptr), michael@0: mOld(nullptr) {} michael@0: michael@0: ~nsAutoFloatManager(); michael@0: michael@0: /** michael@0: * Create a new float manager for the specified frame. This will michael@0: * `remember' the old float manager, and install the new float michael@0: * manager in the reflow state. michael@0: */ michael@0: nsresult michael@0: CreateFloatManager(nsPresContext *aPresContext); michael@0: michael@0: protected: michael@0: nsHTMLReflowState &mReflowState; michael@0: nsFloatManager *mNew; michael@0: nsFloatManager *mOld; michael@0: }; michael@0: michael@0: #endif /* !defined(nsFloatManager_h_) */