1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 // vim:cindent:ts=2:et:sw=2:
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /* class that manages rules for positioning floats */
8 
9 #ifndef nsFloatManager_h_
10 #define nsFloatManager_h_
11 
12 #include "mozilla/Attributes.h"
13 #include "mozilla/WritingModes.h"
14 #include "nsCoord.h"
15 #include "nsFrameList.h" // for DEBUG_FRAME_DUMP
16 #include "nsIntervalSet.h"
17 #include "nsTArray.h"
18 
19 class nsIPresShell;
20 class nsIFrame;
21 class nsPresContext;
22 namespace mozilla {
23 struct ReflowInput;
24 } // namespace mozilla
25 
26 /**
27  * The available space for content not occupied by floats is divided
28  * into a sequence of rectangles in the block direction.  However, we
29  * need to know not only the rectangle, but also whether it was reduced
30  * (from the content rectangle) by floats that actually intruded into
31  * the content rectangle.
32  */
33 struct nsFlowAreaRect {
34   mozilla::LogicalRect mRect;
35   bool mHasFloats;
36 
nsFlowAreaRectnsFlowAreaRect37   nsFlowAreaRect(mozilla::WritingMode aWritingMode,
38                  nscoord aICoord, nscoord aBCoord,
39                  nscoord aISize, nscoord aBSize,
40                  bool aHasFloats)
41     : mRect(aWritingMode, aICoord, aBCoord, aISize, aBSize)
42     , mHasFloats(aHasFloats) {}
43 };
44 
45 #define NS_FLOAT_MANAGER_CACHE_SIZE 4
46 
47 class nsFloatManager {
48 public:
49   explicit nsFloatManager(nsIPresShell* aPresShell, mozilla::WritingMode aWM);
50   ~nsFloatManager();
51 
52   void* operator new(size_t aSize) CPP_THROW_NEW;
53   void operator delete(void* aPtr, size_t aSize);
54 
55   static void Shutdown();
56 
57   /**
58    * Get float region stored on the frame. (Defaults to mRect if it's
59    * not there.) The float region is the area impacted by this float;
60    * the coordinates are relative to the containing block frame.
61    */
62   static mozilla::LogicalRect GetRegionFor(mozilla::WritingMode aWM,
63                                            nsIFrame* aFloatFrame,
64                                            const nsSize& aContainerSize);
65   /**
66    * Calculate the float region for this frame using aMargin and the
67    * frame's mRect. The region includes the margins around the float,
68    * but doesn't include the relative offsets.
69    * Note that if the frame is or has a continuation, aMargin's top
70    * and/or bottom must be zeroed by the caller.
71    */
72   static mozilla::LogicalRect CalculateRegionFor(
73                                 mozilla::WritingMode aWM,
74                                 nsIFrame* aFloatFrame,
75                                 const mozilla::LogicalMargin& aMargin,
76                                 const nsSize& aContainerSize);
77   /**
78    * Store the float region on the frame. The region is stored
79    * as a delta against the mRect, so repositioning the frame will
80    * also reposition the float region.
81    */
82   static void StoreRegionFor(mozilla::WritingMode aWM,
83                              nsIFrame* aFloat,
84                              const mozilla::LogicalRect& aRegion,
85                              const nsSize& aContainerSize);
86 
87   // Structure that stores the current state of a frame manager for
88   // Save/Restore purposes.
89   struct SavedState {
SavedStateSavedState90     explicit SavedState() {}
91   private:
92     uint32_t mFloatInfoCount;
93     nscoord mLineLeft, mBlockStart;
94     bool mPushedLeftFloatPastBreak;
95     bool mPushedRightFloatPastBreak;
96     bool mSplitLeftFloatAcrossBreak;
97     bool mSplitRightFloatAcrossBreak;
98 
99     friend class nsFloatManager;
100   };
101 
102   /**
103    * Translate the current origin by the specified offsets. This
104    * creates a new local coordinate space relative to the current
105    * coordinate space.
106    */
Translate(nscoord aLineLeft,nscoord aBlockStart)107   void Translate(nscoord aLineLeft, nscoord aBlockStart)
108   {
109     mLineLeft += aLineLeft;
110     mBlockStart += aBlockStart;
111   }
112 
113   /**
114    * Returns the current translation from local coordinate space to
115    * world coordinate space. This represents the accumulated calls to
116    * Translate().
117    */
GetTranslation(nscoord & aLineLeft,nscoord & aBlockStart)118   void GetTranslation(nscoord& aLineLeft, nscoord& aBlockStart) const
119   {
120     aLineLeft = mLineLeft;
121     aBlockStart = mBlockStart;
122   }
123 
124   /**
125    * Get information about the area available to content that flows
126    * around floats.  Two different types of space can be requested:
127    *   BAND_FROM_POINT: returns the band containing block-dir coordinate
128    *     |aBCoord| (though actually with the top truncated to begin at
129    *     aBCoord), but up to at most |aBSize| (which may be nscoord_MAX).
130    *     This will return the tallest rectangle whose block start is
131    *     |aBCoord| and in which there are no changes in what floats are
132    *     on the sides of that rectangle, but will limit the block size
133    *     of the rectangle to |aBSize|.  The inline start and end edges
134    *     of the rectangle give the area available for line boxes in that
135    *     space. The inline size of this resulting rectangle will not be
136    *     negative.
137    *   WIDTH_WITHIN_HEIGHT: This returns a rectangle whose block start
138    *     is aBCoord and whose block size is exactly aBSize.  Its inline
139    *     start and end edges give the corresponding edges of the space
140    *     that can be used for line boxes *throughout* that space.  (It
141    *     is possible that more inline space could be used in part of the
142    *     space if a float begins or ends in it.)  The inline size of the
143    *     resulting rectangle can be negative.
144    *
145    * @param aBCoord [in] block-dir coordinate for block start of
146    *           available space desired
147    * @param aBSize [in] see above
148    * @param aContentArea [in] an nsRect representing the content area
149    * @param aState [in] If null, use the current state, otherwise, do
150    *                    computation based only on floats present in the given
151    *                    saved state.
152    * @return An nsFlowAreaRect whose:
153    *           mRect is the resulting rectangle for line boxes.  It will not
154    *             extend beyond aContentArea's inline bounds, but may be
155    *             narrower when floats are present.
156    *          mBandHasFloats is whether there are floats at the sides of the
157    *            return value including those that do not reduce the line box
158    *            inline size at all (because they are entirely in the margins)
159    *
160    * aBCoord and aAvailSpace are positioned relative to the current translation
161    */
162   enum BandInfoType { BAND_FROM_POINT, WIDTH_WITHIN_HEIGHT };
163   nsFlowAreaRect GetFlowArea(mozilla::WritingMode aWM,
164                              nscoord aBCoord, BandInfoType aInfoType,
165                              nscoord aBSize, mozilla::LogicalRect aContentArea,
166                              SavedState* aState,
167                              const nsSize& aContainerSize) const;
168 
169   /**
170    * Add a float that comes after all floats previously added.  Its
171    * block start must be even with or below the top of all previous
172    * floats.
173    *
174    * aMarginRect is relative to the current translation.  The caller
175    * must ensure aMarginRect.height >= 0 and aMarginRect.width >= 0.
176    */
177   nsresult AddFloat(nsIFrame* aFloatFrame,
178                     const mozilla::LogicalRect& aMarginRect,
179                     mozilla::WritingMode aWM, const nsSize& aContainerSize);
180 
181   /**
182    * Notify that we tried to place a float that could not fit at all and
183    * had to be pushed to the next page/column?  (If so, we can't place
184    * any more floats in this page/column because of the rule that the
185    * top of a float cannot be above the top of an earlier float.  It
186    * also means that any clear needs to continue to the next column.)
187    */
SetPushedLeftFloatPastBreak()188   void SetPushedLeftFloatPastBreak()
189     { mPushedLeftFloatPastBreak = true; }
SetPushedRightFloatPastBreak()190   void SetPushedRightFloatPastBreak()
191     { mPushedRightFloatPastBreak = true; }
192 
193   /**
194    * Notify that we split a float, with part of it needing to be pushed
195    * to the next page/column.  (This means that any 'clear' needs to
196    * continue to the next page/column.)
197    */
SetSplitLeftFloatAcrossBreak()198   void SetSplitLeftFloatAcrossBreak()
199     { mSplitLeftFloatAcrossBreak = true; }
SetSplitRightFloatAcrossBreak()200   void SetSplitRightFloatAcrossBreak()
201     { mSplitRightFloatAcrossBreak = true; }
202 
203   /**
204    * Remove the regions associated with this floating frame and its
205    * next-sibling list.  Some of the frames may never have been added;
206    * we just skip those. This is not fully general; it only works as
207    * long as the N frames to be removed are the last N frames to have
208    * been added; if there's a frame in the middle of them that should
209    * not be removed, YOU LOSE.
210    */
211   nsresult RemoveTrailingRegions(nsIFrame* aFrameList);
212 
HasAnyFloats()213   bool HasAnyFloats() const { return !mFloats.IsEmpty(); }
214 
215   /**
216    * Methods for dealing with the propagation of float damage during
217    * reflow.
218    */
HasFloatDamage()219   bool HasFloatDamage() const
220   {
221     return !mFloatDamage.IsEmpty();
222   }
223 
IncludeInDamage(nscoord aIntervalBegin,nscoord aIntervalEnd)224   void IncludeInDamage(nscoord aIntervalBegin, nscoord aIntervalEnd)
225   {
226     mFloatDamage.IncludeInterval(aIntervalBegin + mBlockStart,
227                                  aIntervalEnd + mBlockStart);
228   }
229 
IntersectsDamage(nscoord aIntervalBegin,nscoord aIntervalEnd)230   bool IntersectsDamage(nscoord aIntervalBegin, nscoord aIntervalEnd) const
231   {
232     return mFloatDamage.Intersects(aIntervalBegin + mBlockStart,
233                                    aIntervalEnd + mBlockStart);
234   }
235 
236   /**
237    * Saves the current state of the float manager into aState.
238    */
239   void PushState(SavedState* aState);
240 
241   /**
242    * Restores the float manager to the saved state.
243    *
244    * These states must be managed using stack discipline. PopState can only
245    * be used after PushState has been used to save the state, and it can only
246    * be used once --- although it can be omitted; saved states can be ignored.
247    * States must be popped in the reverse order they were pushed.  A
248    * call to PopState invalidates any saved states Pushed after the
249    * state passed to PopState was pushed.
250    */
251   void PopState(SavedState* aState);
252 
253   /**
254    * Get the block start of the last float placed into the float
255    * manager, to enforce the rule that a float can't be above an earlier
256    * float. Returns the minimum nscoord value if there are no floats.
257    *
258    * The result is relative to the current translation.
259    */
260   nscoord GetLowestFloatTop() const;
261 
262   /**
263    * Return the coordinate of the lowest float matching aBreakType in
264    * this float manager. Returns aBCoord if there are no matching
265    * floats.
266    *
267    * Both aBCoord and the result are relative to the current translation.
268    */
269   enum {
270     // Tell ClearFloats not to push to nscoord_MAX when floats have been
271     // pushed to the next page/column.
272     DONT_CLEAR_PUSHED_FLOATS = (1<<0)
273   };
274   nscoord ClearFloats(nscoord aBCoord, mozilla::StyleClear aBreakType,
275                       uint32_t aFlags = 0) const;
276 
277   /**
278    * Checks if clear would pass into the floats' BFC's next-in-flow,
279    * i.e. whether floats affecting this clear have continuations.
280    */
281   bool ClearContinues(mozilla::StyleClear aBreakType) const;
282 
AssertStateMatches(SavedState * aState)283   void AssertStateMatches(SavedState *aState) const
284   {
285     NS_ASSERTION(aState->mLineLeft == mLineLeft &&
286                  aState->mBlockStart == mBlockStart &&
287                  aState->mPushedLeftFloatPastBreak ==
288                    mPushedLeftFloatPastBreak &&
289                  aState->mPushedRightFloatPastBreak ==
290                    mPushedRightFloatPastBreak &&
291                  aState->mSplitLeftFloatAcrossBreak ==
292                    mSplitLeftFloatAcrossBreak &&
293                  aState->mSplitRightFloatAcrossBreak ==
294                    mSplitRightFloatAcrossBreak &&
295                  aState->mFloatInfoCount == mFloats.Length(),
296                  "float manager state should match saved state");
297   }
298 
299 #ifdef DEBUG_FRAME_DUMP
300   /**
301    * Dump the state of the float manager out to a file.
302    */
303   nsresult List(FILE* out) const;
304 #endif
305 
306 private:
307 
308   struct FloatInfo {
309     nsIFrame *const mFrame;
310     // The lowest block-ends of left/right floats up to and including
311     // this one.
312     nscoord mLeftBEnd, mRightBEnd;
313 
314     FloatInfo(nsIFrame* aFrame, nscoord aLineLeft, nscoord aBStart,
315               nscoord aISize, nscoord aBSize);
316 
LineLeftFloatInfo317     nscoord LineLeft() const { return mRect.x; }
LineRightFloatInfo318     nscoord LineRight() const { return mRect.XMost(); }
ISizeFloatInfo319     nscoord ISize() const { return mRect.width; }
BStartFloatInfo320     nscoord BStart() const { return mRect.y; }
BEndFloatInfo321     nscoord BEnd() const { return mRect.YMost(); }
BSizeFloatInfo322     nscoord BSize() const { return mRect.height; }
IsEmptyFloatInfo323     bool IsEmpty() const { return mRect.IsEmpty(); }
324 
325 #ifdef NS_BUILD_REFCNT_LOGGING
326     FloatInfo(const FloatInfo& aOther);
327     ~FloatInfo();
328 #endif
329 
330     // NB! This is really a logical rect in a writing mode suitable for
331     // placing floats, which is not necessarily the actual writing mode
332     // either of the block which created the frame manager or the block
333     // that is calling the frame manager. The inline coordinates are in
334     // the line-relative axis of the frame manager and its block
335     // coordinates are in the frame manager's real block direction.
336     nsRect mRect;
337   };
338 
339 #ifdef DEBUG
340   mozilla::WritingMode mWritingMode;
341 #endif
342 
343   // Translation from local to global coordinate space.
344   nscoord mLineLeft, mBlockStart;
345   nsTArray<FloatInfo> mFloats;
346   nsIntervalSet   mFloatDamage;
347 
348   // Did we try to place a float that could not fit at all and had to be
349   // pushed to the next page/column?  If so, we can't place any more
350   // floats in this page/column because of the rule that the top of a
351   // float cannot be above the top of an earlier float.  And we also
352   // need to apply this information to 'clear', and thus need to
353   // separate left and right floats.
354   bool mPushedLeftFloatPastBreak;
355   bool mPushedRightFloatPastBreak;
356 
357   // Did we split a float, with part of it needing to be pushed to the
358   // next page/column.  This means that any 'clear' needs to continue to
359   // the next page/column.
360   bool mSplitLeftFloatAcrossBreak;
361   bool mSplitRightFloatAcrossBreak;
362 
363   static int32_t sCachedFloatManagerCount;
364   static void* sCachedFloatManagers[NS_FLOAT_MANAGER_CACHE_SIZE];
365 
366   nsFloatManager(const nsFloatManager&) = delete;
367   void operator=(const nsFloatManager&) = delete;
368 };
369 
370 /**
371  * A helper class to manage maintenance of the float manager during
372  * nsBlockFrame::Reflow. It automatically restores the old float
373  * manager in the reflow input when the object goes out of scope.
374  */
375 class nsAutoFloatManager {
376   using ReflowInput = mozilla::ReflowInput;
377 
378 public:
nsAutoFloatManager(ReflowInput & aReflowInput)379   explicit nsAutoFloatManager(ReflowInput& aReflowInput)
380     : mReflowInput(aReflowInput),
381       mNew(nullptr),
382       mOld(nullptr) {}
383 
384   ~nsAutoFloatManager();
385 
386   /**
387    * Create a new float manager for the specified frame. This will
388    * `remember' the old float manager, and install the new float
389    * manager in the reflow input.
390    */
391   void
392   CreateFloatManager(nsPresContext *aPresContext);
393 
394 protected:
395   ReflowInput &mReflowInput;
396   nsFloatManager *mNew;
397   nsFloatManager *mOld;
398 };
399 
400 #endif /* !defined(nsFloatManager_h_) */
401