1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 #include "nsFloatManager.h"
10
11 #include <algorithm>
12 #include <initializer_list>
13
14 #include "gfxContext.h"
15 #include "mozilla/PresShell.h"
16 #include "mozilla/ReflowInput.h"
17 #include "mozilla/ShapeUtils.h"
18 #include "nsBlockFrame.h"
19 #include "nsDeviceContext.h"
20 #include "nsError.h"
21 #include "nsIFrame.h"
22 #include "nsIFrameInlines.h"
23 #include "nsImageRenderer.h"
24 #include "nsMemory.h"
25
26 using namespace mozilla;
27 using namespace mozilla::image;
28 using namespace mozilla::gfx;
29
30 int32_t nsFloatManager::sCachedFloatManagerCount = 0;
31 void* nsFloatManager::sCachedFloatManagers[NS_FLOAT_MANAGER_CACHE_SIZE];
32
33 /////////////////////////////////////////////////////////////////////////////
34 // nsFloatManager
35
nsFloatManager(PresShell * aPresShell,WritingMode aWM)36 nsFloatManager::nsFloatManager(PresShell* aPresShell, WritingMode aWM)
37 :
38 #ifdef DEBUG
39 mWritingMode(aWM),
40 #endif
41 mLineLeft(0),
42 mBlockStart(0),
43 mFloatDamage(aPresShell),
44 mPushedLeftFloatPastBreak(false),
45 mPushedRightFloatPastBreak(false),
46 mSplitLeftFloatAcrossBreak(false),
47 mSplitRightFloatAcrossBreak(false) {
48 MOZ_COUNT_CTOR(nsFloatManager);
49 }
50
~nsFloatManager()51 nsFloatManager::~nsFloatManager() { MOZ_COUNT_DTOR(nsFloatManager); }
52
53 // static
operator new(size_t aSize)54 void* nsFloatManager::operator new(size_t aSize) noexcept(true) {
55 if (sCachedFloatManagerCount > 0) {
56 // We have cached unused instances of this class, return a cached
57 // instance in stead of always creating a new one.
58 return sCachedFloatManagers[--sCachedFloatManagerCount];
59 }
60
61 // The cache is empty, this means we have to create a new instance using
62 // the global |operator new|.
63 return moz_xmalloc(aSize);
64 }
65
operator delete(void * aPtr,size_t aSize)66 void nsFloatManager::operator delete(void* aPtr, size_t aSize) {
67 if (!aPtr) return;
68 // This float manager is no longer used, if there's still room in
69 // the cache we'll cache this float manager, unless the layout
70 // module was already shut down.
71
72 if (sCachedFloatManagerCount < NS_FLOAT_MANAGER_CACHE_SIZE &&
73 sCachedFloatManagerCount >= 0) {
74 // There's still space in the cache for more instances, put this
75 // instance in the cache in stead of deleting it.
76
77 sCachedFloatManagers[sCachedFloatManagerCount++] = aPtr;
78 return;
79 }
80
81 // The cache is full, or the layout module has been shut down,
82 // delete this float manager.
83 free(aPtr);
84 }
85
86 /* static */
Shutdown()87 void nsFloatManager::Shutdown() {
88 // The layout module is being shut down, clean up the cache and
89 // disable further caching.
90
91 int32_t i;
92
93 for (i = 0; i < sCachedFloatManagerCount; i++) {
94 void* floatManager = sCachedFloatManagers[i];
95 if (floatManager) free(floatManager);
96 }
97
98 // Disable further caching.
99 sCachedFloatManagerCount = -1;
100 }
101
102 #define CHECK_BLOCK_AND_LINE_DIR(aWM) \
103 NS_ASSERTION((aWM).GetBlockDir() == mWritingMode.GetBlockDir() && \
104 (aWM).IsLineInverted() == mWritingMode.IsLineInverted(), \
105 "incompatible writing modes")
106
GetFlowArea(WritingMode aWM,nscoord aBCoord,nscoord aBSize,BandInfoType aBandInfoType,ShapeType aShapeType,LogicalRect aContentArea,SavedState * aState,const nsSize & aContainerSize) const107 nsFlowAreaRect nsFloatManager::GetFlowArea(
108 WritingMode aWM, nscoord aBCoord, nscoord aBSize,
109 BandInfoType aBandInfoType, ShapeType aShapeType, LogicalRect aContentArea,
110 SavedState* aState, const nsSize& aContainerSize) const {
111 CHECK_BLOCK_AND_LINE_DIR(aWM);
112 NS_ASSERTION(aBSize >= 0, "unexpected max block size");
113 NS_ASSERTION(aContentArea.ISize(aWM) >= 0,
114 "unexpected content area inline size");
115
116 nscoord blockStart = aBCoord + mBlockStart;
117 if (blockStart < nscoord_MIN) {
118 NS_WARNING("bad value");
119 blockStart = nscoord_MIN;
120 }
121
122 // Determine the last float that we should consider.
123 uint32_t floatCount;
124 if (aState) {
125 // Use the provided state.
126 floatCount = aState->mFloatInfoCount;
127 MOZ_ASSERT(floatCount <= mFloats.Length(), "bad state");
128 } else {
129 // Use our current state.
130 floatCount = mFloats.Length();
131 }
132
133 // If there are no floats at all, or we're below the last one, return
134 // quickly.
135 if (floatCount == 0 || (mFloats[floatCount - 1].mLeftBEnd <= blockStart &&
136 mFloats[floatCount - 1].mRightBEnd <= blockStart)) {
137 return nsFlowAreaRect(aWM, aContentArea.IStart(aWM), aBCoord,
138 aContentArea.ISize(aWM), aBSize,
139 nsFlowAreaRectFlags::NoFlags);
140 }
141
142 nscoord blockEnd;
143 if (aBSize == nscoord_MAX) {
144 // This warning (and the two below) are possible to hit on pages
145 // with really large objects.
146 NS_WARNING_ASSERTION(aBandInfoType == BandInfoType::BandFromPoint,
147 "bad height");
148 blockEnd = nscoord_MAX;
149 } else {
150 blockEnd = blockStart + aBSize;
151 if (blockEnd < blockStart || blockEnd > nscoord_MAX) {
152 NS_WARNING("bad value");
153 blockEnd = nscoord_MAX;
154 }
155 }
156 nscoord lineLeft = mLineLeft + aContentArea.LineLeft(aWM, aContainerSize);
157 nscoord lineRight = mLineLeft + aContentArea.LineRight(aWM, aContainerSize);
158 if (lineRight < lineLeft) {
159 NS_WARNING("bad value");
160 lineRight = lineLeft;
161 }
162
163 // Walk backwards through the floats until we either hit the front of
164 // the list or we're above |blockStart|.
165 bool haveFloats = false;
166 bool mayWiden = false;
167 for (uint32_t i = floatCount; i > 0; --i) {
168 const FloatInfo& fi = mFloats[i - 1];
169 if (fi.mLeftBEnd <= blockStart && fi.mRightBEnd <= blockStart) {
170 // There aren't any more floats that could intersect this band.
171 break;
172 }
173 if (fi.IsEmpty(aShapeType)) {
174 // Ignore empty float areas.
175 // https://drafts.csswg.org/css-shapes/#relation-to-box-model-and-float-behavior
176 continue;
177 }
178
179 nscoord floatBStart = fi.BStart(aShapeType);
180 nscoord floatBEnd = fi.BEnd(aShapeType);
181 if (blockStart < floatBStart &&
182 aBandInfoType == BandInfoType::BandFromPoint) {
183 // This float is below our band. Shrink our band's height if needed.
184 if (floatBStart < blockEnd) {
185 blockEnd = floatBStart;
186 }
187 }
188 // If blockStart == blockEnd (which happens only with WidthWithinHeight),
189 // we include floats that begin at our 0-height vertical area. We
190 // need to do this to satisfy the invariant that a
191 // WidthWithinHeight call is at least as narrow on both sides as a
192 // BandFromPoint call beginning at its blockStart.
193 else if (blockStart < floatBEnd &&
194 (floatBStart < blockEnd ||
195 (floatBStart == blockEnd && blockStart == blockEnd))) {
196 // This float is in our band.
197
198 // Shrink our band's width if needed.
199 StyleFloat floatStyle = fi.mFrame->StyleDisplay()->mFloat;
200
201 // When aBandInfoType is BandFromPoint, we're only intended to
202 // consider a point along the y axis rather than a band.
203 const nscoord bandBlockEnd =
204 aBandInfoType == BandInfoType::BandFromPoint ? blockStart : blockEnd;
205 if (floatStyle == StyleFloat::Left) {
206 // A left float
207 nscoord lineRightEdge =
208 fi.LineRight(aShapeType, blockStart, bandBlockEnd);
209 if (lineRightEdge > lineLeft) {
210 lineLeft = lineRightEdge;
211 // Only set haveFloats to true if the float is inside our
212 // containing block. This matches the spec for what some
213 // callers want and disagrees for other callers, so we should
214 // probably provide better information at some point.
215 haveFloats = true;
216
217 // Our area may widen in the block direction if this float may
218 // narrow in the block direction.
219 mayWiden = mayWiden || fi.MayNarrowInBlockDirection(aShapeType);
220 }
221 } else {
222 // A right float
223 nscoord lineLeftEdge =
224 fi.LineLeft(aShapeType, blockStart, bandBlockEnd);
225 if (lineLeftEdge < lineRight) {
226 lineRight = lineLeftEdge;
227 // See above.
228 haveFloats = true;
229 mayWiden = mayWiden || fi.MayNarrowInBlockDirection(aShapeType);
230 }
231 }
232
233 // Shrink our band's height if needed.
234 if (floatBEnd < blockEnd &&
235 aBandInfoType == BandInfoType::BandFromPoint) {
236 blockEnd = floatBEnd;
237 }
238 }
239 }
240
241 nscoord blockSize =
242 (blockEnd == nscoord_MAX) ? nscoord_MAX : (blockEnd - blockStart);
243 // convert back from LineLeft/Right to IStart
244 nscoord inlineStart =
245 aWM.IsBidiLTR()
246 ? lineLeft - mLineLeft
247 : mLineLeft - lineRight + LogicalSize(aWM, aContainerSize).ISize(aWM);
248
249 nsFlowAreaRectFlags flags =
250 (haveFloats ? nsFlowAreaRectFlags::HasFloats
251 : nsFlowAreaRectFlags::NoFlags) |
252 (mayWiden ? nsFlowAreaRectFlags::MayWiden : nsFlowAreaRectFlags::NoFlags);
253
254 return nsFlowAreaRect(aWM, inlineStart, blockStart - mBlockStart,
255 lineRight - lineLeft, blockSize, flags);
256 }
257
AddFloat(nsIFrame * aFloatFrame,const LogicalRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)258 void nsFloatManager::AddFloat(nsIFrame* aFloatFrame,
259 const LogicalRect& aMarginRect, WritingMode aWM,
260 const nsSize& aContainerSize) {
261 CHECK_BLOCK_AND_LINE_DIR(aWM);
262 NS_ASSERTION(aMarginRect.ISize(aWM) >= 0, "negative inline size!");
263 NS_ASSERTION(aMarginRect.BSize(aWM) >= 0, "negative block size!");
264
265 FloatInfo info(aFloatFrame, mLineLeft, mBlockStart, aMarginRect, aWM,
266 aContainerSize);
267
268 // Set mLeftBEnd and mRightBEnd.
269 if (HasAnyFloats()) {
270 FloatInfo& tail = mFloats[mFloats.Length() - 1];
271 info.mLeftBEnd = tail.mLeftBEnd;
272 info.mRightBEnd = tail.mRightBEnd;
273 } else {
274 info.mLeftBEnd = nscoord_MIN;
275 info.mRightBEnd = nscoord_MIN;
276 }
277 StyleFloat floatStyle = aFloatFrame->StyleDisplay()->mFloat;
278 MOZ_ASSERT(floatStyle == StyleFloat::Left || floatStyle == StyleFloat::Right,
279 "Unexpected float style!");
280 nscoord& sideBEnd =
281 floatStyle == StyleFloat::Left ? info.mLeftBEnd : info.mRightBEnd;
282 nscoord thisBEnd = info.BEnd();
283 if (thisBEnd > sideBEnd) sideBEnd = thisBEnd;
284
285 mFloats.AppendElement(std::move(info));
286 }
287
288 // static
CalculateRegionFor(WritingMode aWM,nsIFrame * aFloat,const LogicalMargin & aMargin,const nsSize & aContainerSize)289 LogicalRect nsFloatManager::CalculateRegionFor(WritingMode aWM,
290 nsIFrame* aFloat,
291 const LogicalMargin& aMargin,
292 const nsSize& aContainerSize) {
293 // We consider relatively positioned frames at their original position.
294 LogicalRect region(aWM,
295 nsRect(aFloat->GetNormalPosition(), aFloat->GetSize()),
296 aContainerSize);
297
298 // Float region includes its margin
299 region.Inflate(aWM, aMargin);
300
301 // Don't store rectangles with negative margin-box width or height in
302 // the float manager; it can't deal with them.
303 if (region.ISize(aWM) < 0) {
304 // Preserve the right margin-edge for left floats and the left
305 // margin-edge for right floats
306 const nsStyleDisplay* display = aFloat->StyleDisplay();
307 StyleFloat floatStyle = display->mFloat;
308 if ((StyleFloat::Left == floatStyle) == aWM.IsBidiLTR()) {
309 region.IStart(aWM) = region.IEnd(aWM);
310 }
311 region.ISize(aWM) = 0;
312 }
313 if (region.BSize(aWM) < 0) {
314 region.BSize(aWM) = 0;
315 }
316 return region;
317 }
318
NS_DECLARE_FRAME_PROPERTY_DELETABLE(FloatRegionProperty,nsMargin)319 NS_DECLARE_FRAME_PROPERTY_DELETABLE(FloatRegionProperty, nsMargin)
320
321 LogicalRect nsFloatManager::GetRegionFor(WritingMode aWM, nsIFrame* aFloat,
322 const nsSize& aContainerSize) {
323 LogicalRect region = aFloat->GetLogicalRect(aWM, aContainerSize);
324 void* storedRegion = aFloat->GetProperty(FloatRegionProperty());
325 if (storedRegion) {
326 nsMargin margin = *static_cast<nsMargin*>(storedRegion);
327 region.Inflate(aWM, LogicalMargin(aWM, margin));
328 }
329 return region;
330 }
331
StoreRegionFor(WritingMode aWM,nsIFrame * aFloat,const LogicalRect & aRegion,const nsSize & aContainerSize)332 void nsFloatManager::StoreRegionFor(WritingMode aWM, nsIFrame* aFloat,
333 const LogicalRect& aRegion,
334 const nsSize& aContainerSize) {
335 nsRect region = aRegion.GetPhysicalRect(aWM, aContainerSize);
336 nsRect rect = aFloat->GetRect();
337 if (region.IsEqualEdges(rect)) {
338 aFloat->RemoveProperty(FloatRegionProperty());
339 } else {
340 nsMargin* storedMargin = aFloat->GetProperty(FloatRegionProperty());
341 if (!storedMargin) {
342 storedMargin = new nsMargin();
343 aFloat->SetProperty(FloatRegionProperty(), storedMargin);
344 }
345 *storedMargin = region - rect;
346 }
347 }
348
RemoveTrailingRegions(nsIFrame * aFrameList)349 nsresult nsFloatManager::RemoveTrailingRegions(nsIFrame* aFrameList) {
350 if (!aFrameList) {
351 return NS_OK;
352 }
353 // This could be a good bit simpler if we could guarantee that the
354 // floats given were at the end of our list, so we could just search
355 // for the head of aFrameList. (But we can't;
356 // layout/reftests/bugs/421710-1.html crashes.)
357 nsTHashtable<nsPtrHashKey<nsIFrame> > frameSet(1);
358
359 for (nsIFrame* f = aFrameList; f; f = f->GetNextSibling()) {
360 frameSet.PutEntry(f);
361 }
362
363 uint32_t newLength = mFloats.Length();
364 while (newLength > 0) {
365 if (!frameSet.Contains(mFloats[newLength - 1].mFrame)) {
366 break;
367 }
368 --newLength;
369 }
370 mFloats.TruncateLength(newLength);
371
372 #ifdef DEBUG
373 for (uint32_t i = 0; i < mFloats.Length(); ++i) {
374 NS_ASSERTION(
375 !frameSet.Contains(mFloats[i].mFrame),
376 "Frame region deletion was requested but we couldn't delete it");
377 }
378 #endif
379
380 return NS_OK;
381 }
382
PushState(SavedState * aState)383 void nsFloatManager::PushState(SavedState* aState) {
384 MOZ_ASSERT(aState, "Need a place to save state");
385
386 // This is a cheap push implementation, which
387 // only saves the (x,y) and last frame in the mFrameInfoMap
388 // which is enough info to get us back to where we should be
389 // when pop is called.
390 //
391 // This push/pop mechanism is used to undo any
392 // floats that were added during the unconstrained reflow
393 // in nsBlockReflowContext::DoReflowBlock(). (See bug 96736)
394 //
395 // It should also be noted that the state for mFloatDamage is
396 // intentionally not saved or restored in PushState() and PopState(),
397 // since that could lead to bugs where damage is missed/dropped when
398 // we move from position A to B (during the intermediate incremental
399 // reflow mentioned above) and then from B to C during the subsequent
400 // reflow. In the typical case A and C will be the same, but not always.
401 // Allowing mFloatDamage to accumulate the damage incurred during both
402 // reflows ensures that nothing gets missed.
403 aState->mLineLeft = mLineLeft;
404 aState->mBlockStart = mBlockStart;
405 aState->mPushedLeftFloatPastBreak = mPushedLeftFloatPastBreak;
406 aState->mPushedRightFloatPastBreak = mPushedRightFloatPastBreak;
407 aState->mSplitLeftFloatAcrossBreak = mSplitLeftFloatAcrossBreak;
408 aState->mSplitRightFloatAcrossBreak = mSplitRightFloatAcrossBreak;
409 aState->mFloatInfoCount = mFloats.Length();
410 }
411
PopState(SavedState * aState)412 void nsFloatManager::PopState(SavedState* aState) {
413 MOZ_ASSERT(aState, "No state to restore?");
414
415 mLineLeft = aState->mLineLeft;
416 mBlockStart = aState->mBlockStart;
417 mPushedLeftFloatPastBreak = aState->mPushedLeftFloatPastBreak;
418 mPushedRightFloatPastBreak = aState->mPushedRightFloatPastBreak;
419 mSplitLeftFloatAcrossBreak = aState->mSplitLeftFloatAcrossBreak;
420 mSplitRightFloatAcrossBreak = aState->mSplitRightFloatAcrossBreak;
421
422 NS_ASSERTION(aState->mFloatInfoCount <= mFloats.Length(),
423 "somebody misused PushState/PopState");
424 mFloats.TruncateLength(aState->mFloatInfoCount);
425 }
426
GetLowestFloatTop() const427 nscoord nsFloatManager::GetLowestFloatTop() const {
428 if (mPushedLeftFloatPastBreak || mPushedRightFloatPastBreak) {
429 return nscoord_MAX;
430 }
431 if (!HasAnyFloats()) {
432 return nscoord_MIN;
433 }
434 return mFloats[mFloats.Length() - 1].BStart() - mBlockStart;
435 }
436
437 #ifdef DEBUG_FRAME_DUMP
DebugListFloatManager(const nsFloatManager * aFloatManager)438 void DebugListFloatManager(const nsFloatManager* aFloatManager) {
439 aFloatManager->List(stdout);
440 }
441
List(FILE * out) const442 nsresult nsFloatManager::List(FILE* out) const {
443 if (!HasAnyFloats()) return NS_OK;
444
445 for (uint32_t i = 0; i < mFloats.Length(); ++i) {
446 const FloatInfo& fi = mFloats[i];
447 fprintf_stderr(out,
448 "Float %u: frame=%p rect={%d,%d,%d,%d} BEnd={l:%d, r:%d}\n",
449 i, static_cast<void*>(fi.mFrame), fi.LineLeft(), fi.BStart(),
450 fi.ISize(), fi.BSize(), fi.mLeftBEnd, fi.mRightBEnd);
451 }
452 return NS_OK;
453 }
454 #endif
455
ClearFloats(nscoord aBCoord,StyleClear aBreakType,uint32_t aFlags) const456 nscoord nsFloatManager::ClearFloats(nscoord aBCoord, StyleClear aBreakType,
457 uint32_t aFlags) const {
458 if (!(aFlags & DONT_CLEAR_PUSHED_FLOATS) && ClearContinues(aBreakType)) {
459 // FIXME bug 1574046. This makes no sense! we set aState.mBCoord from this
460 // result which will eventually make our parent's size nscoord_MAX!
461 // This is wallpapered over in nsBlockFrame::ComputeFinalSize for now...
462 return nscoord_MAX;
463 }
464 if (!HasAnyFloats()) {
465 return aBCoord;
466 }
467
468 nscoord blockEnd = aBCoord + mBlockStart;
469
470 const FloatInfo& tail = mFloats[mFloats.Length() - 1];
471 switch (aBreakType) {
472 case StyleClear::Both:
473 blockEnd = std::max(blockEnd, tail.mLeftBEnd);
474 blockEnd = std::max(blockEnd, tail.mRightBEnd);
475 break;
476 case StyleClear::Left:
477 blockEnd = std::max(blockEnd, tail.mLeftBEnd);
478 break;
479 case StyleClear::Right:
480 blockEnd = std::max(blockEnd, tail.mRightBEnd);
481 break;
482 default:
483 // Do nothing
484 break;
485 }
486
487 blockEnd -= mBlockStart;
488
489 return blockEnd;
490 }
491
ClearContinues(StyleClear aBreakType) const492 bool nsFloatManager::ClearContinues(StyleClear aBreakType) const {
493 return ((mPushedLeftFloatPastBreak || mSplitLeftFloatAcrossBreak) &&
494 (aBreakType == StyleClear::Both || aBreakType == StyleClear::Left)) ||
495 ((mPushedRightFloatPastBreak || mSplitRightFloatAcrossBreak) &&
496 (aBreakType == StyleClear::Both || aBreakType == StyleClear::Right));
497 }
498
499 /////////////////////////////////////////////////////////////////////////////
500 // ShapeInfo is an abstract class for implementing all the shapes in CSS
501 // Shapes Module. A subclass needs to override all the methods to adjust
502 // the flow area with respect to its shape.
503 //
504 class nsFloatManager::ShapeInfo {
505 public:
506 virtual ~ShapeInfo() = default;
507
508 virtual nscoord LineLeft(const nscoord aBStart,
509 const nscoord aBEnd) const = 0;
510 virtual nscoord LineRight(const nscoord aBStart,
511 const nscoord aBEnd) const = 0;
512 virtual nscoord BStart() const = 0;
513 virtual nscoord BEnd() const = 0;
514 virtual bool IsEmpty() const = 0;
515
516 // Does this shape possibly get inline narrower in the BStart() to BEnd()
517 // span when proceeding in the block direction? This is false for unrounded
518 // rectangles that span all the way to BEnd(), but could be true for other
519 // shapes. Note that we don't care if the BEnd() falls short of the margin
520 // rect -- the ShapeInfo can only affect float behavior in the span between
521 // BStart() and BEnd().
522 virtual bool MayNarrowInBlockDirection() const = 0;
523
524 // Translate the current origin by the specified offsets.
525 virtual void Translate(nscoord aLineLeft, nscoord aBlockStart) = 0;
526
527 static LogicalRect ComputeShapeBoxRect(StyleShapeBox, nsIFrame* const aFrame,
528 const LogicalRect& aMarginRect,
529 WritingMode aWM);
530
531 // Convert the LogicalRect to the special logical coordinate space used
532 // in float manager.
ConvertToFloatLogical(const LogicalRect & aRect,WritingMode aWM,const nsSize & aContainerSize)533 static nsRect ConvertToFloatLogical(const LogicalRect& aRect, WritingMode aWM,
534 const nsSize& aContainerSize) {
535 return nsRect(aRect.LineLeft(aWM, aContainerSize), aRect.BStart(aWM),
536 aRect.ISize(aWM), aRect.BSize(aWM));
537 }
538
539 static UniquePtr<ShapeInfo> CreateShapeBox(nsIFrame* const aFrame,
540 nscoord aShapeMargin,
541 const LogicalRect& aShapeBoxRect,
542 WritingMode aWM,
543 const nsSize& aContainerSize);
544
545 static UniquePtr<ShapeInfo> CreateBasicShape(
546 const StyleBasicShape& aBasicShape, nscoord aShapeMargin,
547 nsIFrame* const aFrame, const LogicalRect& aShapeBoxRect,
548 const LogicalRect& aMarginRect, WritingMode aWM,
549 const nsSize& aContainerSize);
550
551 static UniquePtr<ShapeInfo> CreateInset(const StyleBasicShape& aBasicShape,
552 nscoord aShapeMargin,
553 nsIFrame* aFrame,
554 const LogicalRect& aShapeBoxRect,
555 WritingMode aWM,
556 const nsSize& aContainerSize);
557
558 static UniquePtr<ShapeInfo> CreateCircleOrEllipse(
559 const StyleBasicShape& aBasicShape, nscoord aShapeMargin,
560 nsIFrame* const aFrame, const LogicalRect& aShapeBoxRect, WritingMode aWM,
561 const nsSize& aContainerSize);
562
563 static UniquePtr<ShapeInfo> CreatePolygon(const StyleBasicShape& aBasicShape,
564 nscoord aShapeMargin,
565 nsIFrame* const aFrame,
566 const LogicalRect& aShapeBoxRect,
567 const LogicalRect& aMarginRect,
568 WritingMode aWM,
569 const nsSize& aContainerSize);
570
571 static UniquePtr<ShapeInfo> CreateImageShape(const StyleImage& aShapeImage,
572 float aShapeImageThreshold,
573 nscoord aShapeMargin,
574 nsIFrame* const aFrame,
575 const LogicalRect& aMarginRect,
576 WritingMode aWM,
577 const nsSize& aContainerSize);
578
579 protected:
580 // Compute the minimum line-axis difference between the bounding shape
581 // box and its rounded corner within the given band (block-axis region).
582 // This is used as a helper function to compute the LineRight() and
583 // LineLeft(). See the picture in the implementation for an example.
584 // RadiusL and RadiusB stand for radius on the line-axis and block-axis.
585 //
586 // Returns radius-x diff on the line-axis, or 0 if there's no rounded
587 // corner within the given band.
588 static nscoord ComputeEllipseLineInterceptDiff(
589 const nscoord aShapeBoxBStart, const nscoord aShapeBoxBEnd,
590 const nscoord aBStartCornerRadiusL, const nscoord aBStartCornerRadiusB,
591 const nscoord aBEndCornerRadiusL, const nscoord aBEndCornerRadiusB,
592 const nscoord aBandBStart, const nscoord aBandBEnd);
593
594 static nscoord XInterceptAtY(const nscoord aY, const nscoord aRadiusX,
595 const nscoord aRadiusY);
596
597 // Convert the physical point to the special logical coordinate space
598 // used in float manager.
599 static nsPoint ConvertToFloatLogical(const nsPoint& aPoint, WritingMode aWM,
600 const nsSize& aContainerSize);
601
602 // Convert the half corner radii (nscoord[8]) to the special logical
603 // coordinate space used in float manager.
604 static UniquePtr<nscoord[]> ConvertToFloatLogical(const nscoord aRadii[8],
605 WritingMode aWM);
606
607 // Some ShapeInfo subclasses may define their float areas in intervals.
608 // Each interval is a rectangle that is one device pixel deep in the block
609 // axis. The values are stored as block edges in the y coordinates,
610 // and inline edges as the x coordinates. Interval arrays should be sorted
611 // on increasing y values. This function uses a binary search to find the
612 // first interval that contains aTargetY. If no such interval exists, this
613 // function returns aIntervals.Length().
614 static size_t MinIntervalIndexContainingY(const nsTArray<nsRect>& aIntervals,
615 const nscoord aTargetY);
616
617 // This interval function is designed to handle the arguments to ::LineLeft()
618 // and LineRight() and interpret them for the supplied aIntervals.
619 static nscoord LineEdge(const nsTArray<nsRect>& aIntervals,
620 const nscoord aBStart, const nscoord aBEnd,
621 bool aIsLineLeft);
622
623 // These types, constants, and functions are useful for ShapeInfos that
624 // allocate a distance field. Efficient distance field calculations use
625 // integer values that are 5X the Euclidean distance. MAX_MARGIN_5X is the
626 // largest possible margin that we can calculate (in 5X integer dev pixels),
627 // given these constraints.
628 typedef uint16_t dfType;
629 static const dfType MAX_CHAMFER_VALUE;
630 static const dfType MAX_MARGIN;
631 static const dfType MAX_MARGIN_5X;
632
633 // This function returns a typed, overflow-safe value of aShapeMargin in
634 // 5X integer dev pixels.
635 static dfType CalcUsedShapeMargin5X(nscoord aShapeMargin,
636 int32_t aAppUnitsPerDevPixel);
637 };
638
639 const nsFloatManager::ShapeInfo::dfType
640 nsFloatManager::ShapeInfo::MAX_CHAMFER_VALUE = 11;
641
642 const nsFloatManager::ShapeInfo::dfType nsFloatManager::ShapeInfo::MAX_MARGIN =
643 (std::numeric_limits<dfType>::max() - MAX_CHAMFER_VALUE) / 5;
644
645 const nsFloatManager::ShapeInfo::dfType
646 nsFloatManager::ShapeInfo::MAX_MARGIN_5X = MAX_MARGIN * 5;
647
648 /////////////////////////////////////////////////////////////////////////////
649 // EllipseShapeInfo
650 //
651 // Implements shape-outside: circle() and shape-outside: ellipse().
652 //
653 class nsFloatManager::EllipseShapeInfo final
654 : public nsFloatManager::ShapeInfo {
655 public:
656 // Construct the float area using math to calculate the shape boundary.
657 // This is the fast path and should be used when shape-margin is negligible,
658 // or when the two values of aRadii are roughly equal. Those two conditions
659 // are defined by ShapeMarginIsNegligible() and RadiiAreRoughlyEqual(). In
660 // those cases, we can conveniently represent the entire float area using
661 // an ellipse.
662 EllipseShapeInfo(const nsPoint& aCenter, const nsSize& aRadii,
663 nscoord aShapeMargin);
664
665 // Construct the float area using rasterization to calculate the shape
666 // boundary. This constructor accounts for the fact that applying
667 // 'shape-margin' to an ellipse produces a shape that is not mathematically
668 // representable as an ellipse.
669 EllipseShapeInfo(const nsPoint& aCenter, const nsSize& aRadii,
670 nscoord aShapeMargin, int32_t aAppUnitsPerDevPixel);
671
ShapeMarginIsNegligible(nscoord aShapeMargin)672 static bool ShapeMarginIsNegligible(nscoord aShapeMargin) {
673 // For now, only return true for a shape-margin of 0. In the future, if
674 // we want to enable use of the fast-path constructor more often, this
675 // limit could be increased;
676 static const nscoord SHAPE_MARGIN_NEGLIGIBLE_MAX(0);
677 return aShapeMargin <= SHAPE_MARGIN_NEGLIGIBLE_MAX;
678 }
679
RadiiAreRoughlyEqual(const nsSize & aRadii)680 static bool RadiiAreRoughlyEqual(const nsSize& aRadii) {
681 // For now, only return true when we are exactly equal. In the future, if
682 // we want to enable use of the fast-path constructor more often, this
683 // could be generalized to allow radii that are in some close proportion
684 // to each other.
685 return aRadii.width == aRadii.height;
686 }
687 nscoord LineEdge(const nscoord aBStart, const nscoord aBEnd,
688 bool aLeft) const;
689 nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
690 nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
BStart() const691 nscoord BStart() const override {
692 return mCenter.y - mRadii.height - mShapeMargin;
693 }
BEnd() const694 nscoord BEnd() const override {
695 return mCenter.y + mRadii.height + mShapeMargin;
696 }
IsEmpty() const697 bool IsEmpty() const override {
698 // An EllipseShapeInfo is never empty, because an ellipse or circle with
699 // a zero radius acts like a point, and an ellipse with one zero radius
700 // acts like a line.
701 return false;
702 }
MayNarrowInBlockDirection() const703 bool MayNarrowInBlockDirection() const override { return true; }
704
Translate(nscoord aLineLeft,nscoord aBlockStart)705 void Translate(nscoord aLineLeft, nscoord aBlockStart) override {
706 mCenter.MoveBy(aLineLeft, aBlockStart);
707
708 for (nsRect& interval : mIntervals) {
709 interval.MoveBy(aLineLeft, aBlockStart);
710 }
711 }
712
713 private:
714 // The position of the center of the ellipse. The coordinate space is the
715 // same as FloatInfo::mRect.
716 nsPoint mCenter;
717 // The radii of the ellipse in app units. The width and height represent
718 // the line-axis and block-axis radii of the ellipse.
719 nsSize mRadii;
720 // The shape-margin of the ellipse in app units. If this value is greater
721 // than zero, then we calculate the bounds of the ellipse + margin using
722 // numerical methods and store the values in mIntervals.
723 nscoord mShapeMargin;
724
725 // An interval is slice of the float area defined by this EllipseShapeInfo.
726 // Each interval is a rectangle that is one pixel deep in the block
727 // axis. The values are stored as block edges in the y coordinates,
728 // and inline edges as the x coordinates.
729
730 // The intervals are stored in ascending order on y.
731 nsTArray<nsRect> mIntervals;
732 };
733
EllipseShapeInfo(const nsPoint & aCenter,const nsSize & aRadii,nscoord aShapeMargin)734 nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
735 const nsSize& aRadii,
736 nscoord aShapeMargin)
737 : mCenter(aCenter),
738 mRadii(aRadii),
739 mShapeMargin(
740 0) // We intentionally ignore the value of aShapeMargin here.
741 {
742 MOZ_ASSERT(
743 RadiiAreRoughlyEqual(aRadii) || ShapeMarginIsNegligible(aShapeMargin),
744 "This constructor should only be called when margin is "
745 "negligible or radii are roughly equal.");
746
747 // We add aShapeMargin into the radii, and we earlier stored a mShapeMargin
748 // of zero.
749 mRadii.width += aShapeMargin;
750 mRadii.height += aShapeMargin;
751 }
752
EllipseShapeInfo(const nsPoint & aCenter,const nsSize & aRadii,nscoord aShapeMargin,int32_t aAppUnitsPerDevPixel)753 nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
754 const nsSize& aRadii,
755 nscoord aShapeMargin,
756 int32_t aAppUnitsPerDevPixel)
757 : mCenter(aCenter), mRadii(aRadii), mShapeMargin(aShapeMargin) {
758 if (RadiiAreRoughlyEqual(aRadii) || ShapeMarginIsNegligible(aShapeMargin)) {
759 // Mimic the behavior of the simple constructor, by adding aShapeMargin
760 // into the radii, and then storing mShapeMargin of zero.
761 mRadii.width += mShapeMargin;
762 mRadii.height += mShapeMargin;
763 mShapeMargin = 0;
764 return;
765 }
766
767 // We have to calculate a distance field from the ellipse edge, then build
768 // intervals based on pixels with less than aShapeMargin distance to an
769 // edge pixel.
770
771 // mCenter and mRadii have already been translated into logical coordinates.
772 // x = inline, y = block. Due to symmetry, we only need to calculate the
773 // distance field for one quadrant of the ellipse. We choose the positive-x,
774 // positive-y quadrant (the lower right quadrant in horizontal-tb writing
775 // mode). We choose this quadrant because it allows us to traverse our
776 // distance field in memory order, which is more cache efficient.
777 // When we apply these intervals in LineLeft() and LineRight(), we
778 // account for block ranges that hit other quadrants, or hit multiple
779 // quadrants.
780
781 // Given this setup, computing the distance field is a one-pass O(n)
782 // operation that runs from block top-to-bottom, inline left-to-right. We
783 // use a chamfer 5-7-11 5x5 matrix to compute minimum distance to an edge
784 // pixel. This integer math computation is reasonably close to the true
785 // Euclidean distance. The distances will be approximately 5x the true
786 // distance, quantized in integer units. The 5x is factored away in the
787 // comparison which builds the intervals.
788 dfType usedMargin5X =
789 CalcUsedShapeMargin5X(aShapeMargin, aAppUnitsPerDevPixel);
790
791 // Calculate the bounds of one quadrant of the ellipse, in integer device
792 // pixels. These bounds are equal to the rectangle defined by the radii,
793 // plus the shape-margin value in both dimensions.
794 const LayoutDeviceIntSize bounds =
795 LayoutDevicePixel::FromAppUnitsRounded(mRadii, aAppUnitsPerDevPixel) +
796 LayoutDeviceIntSize(usedMargin5X / 5, usedMargin5X / 5);
797
798 // Since our distance field is computed with a 5x5 neighborhood, but only
799 // looks in the negative block and negative inline directions, it is
800 // effectively a 3x3 neighborhood. We need to expand our distance field
801 // outwards by a further 2 pixels in both axes (on the minimum block edge
802 // and the minimum inline edge). We call this edge area the expanded region.
803
804 static const uint32_t iExpand = 2;
805 static const uint32_t bExpand = 2;
806
807 // Clamp the size of our distance field sizes to prevent multiplication
808 // overflow.
809 static const uint32_t DF_SIDE_MAX =
810 floor(sqrt((double)(std::numeric_limits<int32_t>::max())));
811 const uint32_t iSize = std::min(bounds.width + iExpand, DF_SIDE_MAX);
812 const uint32_t bSize = std::min(bounds.height + bExpand, DF_SIDE_MAX);
813 auto df = MakeUniqueFallible<dfType[]>(iSize * bSize);
814 if (!df) {
815 // Without a distance field, we can't reason about the float area.
816 return;
817 }
818
819 // Single pass setting distance field, in positive block direction, three
820 // cases:
821 // 1) Expanded region pixel: set to MAX_MARGIN_5X.
822 // 2) Pixel within the ellipse: set to 0.
823 // 3) Other pixel: set to minimum neighborhood distance value, computed
824 // with 5-7-11 chamfer.
825
826 for (uint32_t b = 0; b < bSize; ++b) {
827 bool bIsInExpandedRegion(b < bExpand);
828 nscoord bInAppUnits = (b - bExpand) * aAppUnitsPerDevPixel;
829 bool bIsMoreThanEllipseBEnd(bInAppUnits > mRadii.height);
830
831 // Find the i intercept of the ellipse edge for this block row, and
832 // adjust it to compensate for the expansion of the inline dimension.
833 // If we're in the expanded region, or if we're using a b that's more
834 // than the bEnd of the ellipse, the intercept is nscoord_MIN.
835 // We have one other special case to consider: when the ellipse has no
836 // height. In that case we treat the bInAppUnits == 0 case as
837 // intercepting at the width of the ellipse. All other cases solve
838 // the intersection mathematically.
839 const int32_t iIntercept =
840 (bIsInExpandedRegion || bIsMoreThanEllipseBEnd)
841 ? nscoord_MIN
842 : iExpand + NSAppUnitsToIntPixels(
843 (!!mRadii.height || bInAppUnits)
844 ? XInterceptAtY(bInAppUnits, mRadii.width,
845 mRadii.height)
846 : mRadii.width,
847 aAppUnitsPerDevPixel);
848
849 // Set iMax in preparation for this block row.
850 int32_t iMax = iIntercept;
851
852 for (uint32_t i = 0; i < iSize; ++i) {
853 const uint32_t index = i + b * iSize;
854 MOZ_ASSERT(index < (iSize * bSize),
855 "Our distance field index should be in-bounds.");
856
857 // Handle our three cases, in order.
858 if (i < iExpand || bIsInExpandedRegion) {
859 // Case 1: Expanded reqion pixel.
860 df[index] = MAX_MARGIN_5X;
861 } else if ((int32_t)i <= iIntercept) {
862 // Case 2: Pixel within the ellipse, or just outside the edge of it.
863 // Having a positive height indicates that there's an area we can
864 // be inside of.
865 df[index] = (!!mRadii.height) ? 0 : 5;
866 } else {
867 // Case 3: Other pixel.
868
869 // Backward-looking neighborhood distance from target pixel X
870 // with chamfer 5-7-11 looks like:
871 //
872 // +--+--+--+
873 // | |11| |
874 // +--+--+--+
875 // |11| 7| 5|
876 // +--+--+--+
877 // | | 5| X|
878 // +--+--+--+
879 //
880 // X should be set to the minimum of the values of all of the numbered
881 // neighbors summed with the value in that chamfer cell.
882 MOZ_ASSERT(index - iSize - 2 < (iSize * bSize) &&
883 index - (iSize * 2) - 1 < (iSize * bSize),
884 "Our distance field most extreme indices should be "
885 "in-bounds.");
886
887 // clang-format off
888 df[index] = std::min<dfType>(df[index - 1] + 5,
889 std::min<dfType>(df[index - iSize] + 5,
890 std::min<dfType>(df[index - iSize - 1] + 7,
891 std::min<dfType>(df[index - iSize - 2] + 11,
892 df[index - (iSize * 2) - 1] + 11))));
893 // clang-format on
894
895 // Check the df value and see if it's less than or equal to the
896 // usedMargin5X value.
897 if (df[index] <= usedMargin5X) {
898 MOZ_ASSERT(iMax < (int32_t)i);
899 iMax = i;
900 } else {
901 // Since we're computing the bottom-right quadrant, there's no way
902 // for a later i value in this row to be within the usedMargin5X
903 // value. Likewise, every row beyond us will encounter this
904 // condition with an i value less than or equal to our i value now.
905 // Since our chamfer only looks upward and leftward, we can stop
906 // calculating for the rest of the row, because the distance field
907 // values there will never be looked at in a later row's chamfer
908 // calculation.
909 break;
910 }
911 }
912 }
913
914 // It's very likely, though not guaranteed that we will find an pixel
915 // within the shape-margin distance for each block row. This may not
916 // always be true due to rounding errors.
917 if (iMax > nscoord_MIN) {
918 // Origin for this interval is at the center of the ellipse, adjusted
919 // in the positive block direction by bInAppUnits.
920 nsPoint origin(aCenter.x, aCenter.y + bInAppUnits);
921 // Size is an inline iMax plus 1 (to account for the whole pixel) dev
922 // pixels, by 1 block dev pixel. We convert this to app units.
923 nsSize size((iMax - iExpand + 1) * aAppUnitsPerDevPixel,
924 aAppUnitsPerDevPixel);
925 mIntervals.AppendElement(nsRect(origin, size));
926 }
927 }
928 }
929
LineEdge(const nscoord aBStart,const nscoord aBEnd,bool aIsLineLeft) const930 nscoord nsFloatManager::EllipseShapeInfo::LineEdge(const nscoord aBStart,
931 const nscoord aBEnd,
932 bool aIsLineLeft) const {
933 // If no mShapeMargin, just compute the edge using math.
934 if (mShapeMargin == 0) {
935 nscoord lineDiff = ComputeEllipseLineInterceptDiff(
936 BStart(), BEnd(), mRadii.width, mRadii.height, mRadii.width,
937 mRadii.height, aBStart, aBEnd);
938 return mCenter.x + (aIsLineLeft ? (-mRadii.width + lineDiff)
939 : (mRadii.width - lineDiff));
940 }
941
942 // We are checking against our intervals. Make sure we have some.
943 if (mIntervals.IsEmpty()) {
944 NS_WARNING("With mShapeMargin > 0, we can't proceed without intervals.");
945 return aIsLineLeft ? nscoord_MAX : nscoord_MIN;
946 }
947
948 // Map aBStart and aBEnd into our intervals. Our intervals are calculated
949 // for the lower-right quadrant (in terms of horizontal-tb writing mode).
950 // If aBStart and aBEnd span the center of the ellipse, then we know we
951 // are at the maximum displacement from the center.
952 bool bStartIsAboveCenter = (aBStart < mCenter.y);
953 bool bEndIsBelowOrAtCenter = (aBEnd >= mCenter.y);
954 if (bStartIsAboveCenter && bEndIsBelowOrAtCenter) {
955 return mCenter.x + (aIsLineLeft ? (-mRadii.width - mShapeMargin)
956 : (mRadii.width + mShapeMargin));
957 }
958
959 // aBStart and aBEnd don't span the center. Since the intervals are
960 // strictly wider approaching the center (the start of the mIntervals
961 // array), we only need to find the interval at the block value closest to
962 // the center. We find the min of aBStart, aBEnd, and their reflections --
963 // whichever two of them are within the lower-right quadrant. When we
964 // reflect from the upper-right quadrant to the lower-right, we have to
965 // subtract 1 from the reflection, to account that block values are always
966 // addressed from the leading block edge.
967
968 // The key example is when we check with aBStart == aBEnd at the top of the
969 // intervals. That block line would be considered contained in the
970 // intervals (though it has no height), but its reflection would not be
971 // within the intervals unless we subtract 1.
972 nscoord bSmallestWithinIntervals = std::min(
973 bStartIsAboveCenter ? aBStart + (mCenter.y - aBStart) * 2 - 1 : aBStart,
974 bEndIsBelowOrAtCenter ? aBEnd : aBEnd + (mCenter.y - aBEnd) * 2 - 1);
975
976 MOZ_ASSERT(bSmallestWithinIntervals >= mCenter.y &&
977 bSmallestWithinIntervals < BEnd(),
978 "We should have a block value within the float area.");
979
980 size_t index =
981 MinIntervalIndexContainingY(mIntervals, bSmallestWithinIntervals);
982 if (index >= mIntervals.Length()) {
983 // This indicates that our intervals don't cover the block value
984 // bSmallestWithinIntervals. This can happen when rounding error in the
985 // distance field calculation resulted in the last block pixel row not
986 // contributing to the float area. As long as we're within one block pixel
987 // past the last interval, this is an expected outcome.
988 #ifdef DEBUG
989 nscoord onePixelPastLastInterval =
990 mIntervals[mIntervals.Length() - 1].YMost() +
991 mIntervals[mIntervals.Length() - 1].Height();
992 NS_WARNING_ASSERTION(bSmallestWithinIntervals < onePixelPastLastInterval,
993 "We should have found a matching interval for this "
994 "block value.");
995 #endif
996 return aIsLineLeft ? nscoord_MAX : nscoord_MIN;
997 }
998
999 // The interval is storing the line right value. If aIsLineLeft is true,
1000 // return the line right value reflected about the center. Since this is
1001 // an inline measurement, it's just checking the distance to an edge, and
1002 // not a collision with a specific pixel. For that reason, we don't need
1003 // to subtract 1 from the reflection, as we did with the block reflection.
1004 nscoord iLineRight = mIntervals[index].XMost();
1005 return aIsLineLeft ? iLineRight - (iLineRight - mCenter.x) * 2 : iLineRight;
1006 }
1007
LineLeft(const nscoord aBStart,const nscoord aBEnd) const1008 nscoord nsFloatManager::EllipseShapeInfo::LineLeft(const nscoord aBStart,
1009 const nscoord aBEnd) const {
1010 return LineEdge(aBStart, aBEnd, true);
1011 }
1012
LineRight(const nscoord aBStart,const nscoord aBEnd) const1013 nscoord nsFloatManager::EllipseShapeInfo::LineRight(const nscoord aBStart,
1014 const nscoord aBEnd) const {
1015 return LineEdge(aBStart, aBEnd, false);
1016 }
1017
1018 /////////////////////////////////////////////////////////////////////////////
1019 // RoundedBoxShapeInfo
1020 //
1021 // Implements shape-outside: <shape-box> and shape-outside: inset().
1022 //
1023 class nsFloatManager::RoundedBoxShapeInfo final
1024 : public nsFloatManager::ShapeInfo {
1025 public:
RoundedBoxShapeInfo(const nsRect & aRect,UniquePtr<nscoord[]> aRadii)1026 RoundedBoxShapeInfo(const nsRect& aRect, UniquePtr<nscoord[]> aRadii)
1027 : mRect(aRect), mRadii(std::move(aRadii)), mShapeMargin(0) {}
1028
1029 RoundedBoxShapeInfo(const nsRect& aRect, UniquePtr<nscoord[]> aRadii,
1030 nscoord aShapeMargin, int32_t aAppUnitsPerDevPixel);
1031
1032 nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
1033 nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
BStart() const1034 nscoord BStart() const override { return mRect.y; }
BEnd() const1035 nscoord BEnd() const override { return mRect.YMost(); }
IsEmpty() const1036 bool IsEmpty() const override {
1037 // A RoundedBoxShapeInfo is never empty, because if it is collapsed to
1038 // zero area, it acts like a point. If it is collapsed further, to become
1039 // inside-out, it acts like a rect in the same shape as the inside-out
1040 // rect.
1041 return false;
1042 }
MayNarrowInBlockDirection() const1043 bool MayNarrowInBlockDirection() const override {
1044 // Only possible to narrow if there are non-null mRadii.
1045 return !!mRadii;
1046 }
1047
Translate(nscoord aLineLeft,nscoord aBlockStart)1048 void Translate(nscoord aLineLeft, nscoord aBlockStart) override {
1049 mRect.MoveBy(aLineLeft, aBlockStart);
1050
1051 if (mShapeMargin > 0) {
1052 MOZ_ASSERT(mLogicalTopLeftCorner && mLogicalTopRightCorner &&
1053 mLogicalBottomLeftCorner && mLogicalBottomRightCorner,
1054 "If we have positive shape-margin, we should have corners.");
1055 mLogicalTopLeftCorner->Translate(aLineLeft, aBlockStart);
1056 mLogicalTopRightCorner->Translate(aLineLeft, aBlockStart);
1057 mLogicalBottomLeftCorner->Translate(aLineLeft, aBlockStart);
1058 mLogicalBottomRightCorner->Translate(aLineLeft, aBlockStart);
1059 }
1060 }
1061
EachCornerHasBalancedRadii(const nscoord * aRadii)1062 static bool EachCornerHasBalancedRadii(const nscoord* aRadii) {
1063 return (aRadii[eCornerTopLeftX] == aRadii[eCornerTopLeftY] &&
1064 aRadii[eCornerTopRightX] == aRadii[eCornerTopRightY] &&
1065 aRadii[eCornerBottomLeftX] == aRadii[eCornerBottomLeftY] &&
1066 aRadii[eCornerBottomRightX] == aRadii[eCornerBottomRightY]);
1067 }
1068
1069 private:
1070 // The rect of the rounded box shape in the float manager's coordinate
1071 // space.
1072 nsRect mRect;
1073 // The half corner radii of the reference box. It's an nscoord[8] array
1074 // in the float manager's coordinate space. If there are no radii, it's
1075 // nullptr.
1076 const UniquePtr<nscoord[]> mRadii;
1077
1078 // A shape-margin value extends the boundaries of the float area. When our
1079 // first constructor is used, it is for the creation of rounded boxes that
1080 // can ignore shape-margin -- either because it was specified as zero or
1081 // because the box shape and radii can be inflated to account for it. When
1082 // our second constructor is used, we store the shape-margin value here.
1083 const nscoord mShapeMargin;
1084
1085 // If our second constructor is called (which implies mShapeMargin > 0),
1086 // we will construct EllipseShapeInfo objects for each corner. We use the
1087 // float logical naming here, where LogicalTopLeftCorner means the BStart
1088 // LineLeft corner, and similarly for the other corners.
1089 UniquePtr<EllipseShapeInfo> mLogicalTopLeftCorner;
1090 UniquePtr<EllipseShapeInfo> mLogicalTopRightCorner;
1091 UniquePtr<EllipseShapeInfo> mLogicalBottomLeftCorner;
1092 UniquePtr<EllipseShapeInfo> mLogicalBottomRightCorner;
1093 };
1094
RoundedBoxShapeInfo(const nsRect & aRect,UniquePtr<nscoord[]> aRadii,nscoord aShapeMargin,int32_t aAppUnitsPerDevPixel)1095 nsFloatManager::RoundedBoxShapeInfo::RoundedBoxShapeInfo(
1096 const nsRect& aRect, UniquePtr<nscoord[]> aRadii, nscoord aShapeMargin,
1097 int32_t aAppUnitsPerDevPixel)
1098 : mRect(aRect), mRadii(std::move(aRadii)), mShapeMargin(aShapeMargin) {
1099 MOZ_ASSERT(mShapeMargin > 0 && !EachCornerHasBalancedRadii(mRadii.get()),
1100 "Slow constructor should only be used for for shape-margin > 0 "
1101 "and radii with elliptical corners.");
1102
1103 // Before we inflate mRect by mShapeMargin, construct each of our corners.
1104 // If we do it in this order, it's a bit simpler to calculate the center
1105 // of each of the corners.
1106 mLogicalTopLeftCorner = MakeUnique<EllipseShapeInfo>(
1107 nsPoint(mRect.X() + mRadii[eCornerTopLeftX],
1108 mRect.Y() + mRadii[eCornerTopLeftY]),
1109 nsSize(mRadii[eCornerTopLeftX], mRadii[eCornerTopLeftY]), mShapeMargin,
1110 aAppUnitsPerDevPixel);
1111
1112 mLogicalTopRightCorner = MakeUnique<EllipseShapeInfo>(
1113 nsPoint(mRect.XMost() - mRadii[eCornerTopRightX],
1114 mRect.Y() + mRadii[eCornerTopRightY]),
1115 nsSize(mRadii[eCornerTopRightX], mRadii[eCornerTopRightY]), mShapeMargin,
1116 aAppUnitsPerDevPixel);
1117
1118 mLogicalBottomLeftCorner = MakeUnique<EllipseShapeInfo>(
1119 nsPoint(mRect.X() + mRadii[eCornerBottomLeftX],
1120 mRect.YMost() - mRadii[eCornerBottomLeftY]),
1121 nsSize(mRadii[eCornerBottomLeftX], mRadii[eCornerBottomLeftY]),
1122 mShapeMargin, aAppUnitsPerDevPixel);
1123
1124 mLogicalBottomRightCorner = MakeUnique<EllipseShapeInfo>(
1125 nsPoint(mRect.XMost() - mRadii[eCornerBottomRightX],
1126 mRect.YMost() - mRadii[eCornerBottomRightY]),
1127 nsSize(mRadii[eCornerBottomRightX], mRadii[eCornerBottomRightY]),
1128 mShapeMargin, aAppUnitsPerDevPixel);
1129
1130 // Now we inflate our mRect by mShapeMargin.
1131 mRect.Inflate(mShapeMargin);
1132 }
1133
LineLeft(const nscoord aBStart,const nscoord aBEnd) const1134 nscoord nsFloatManager::RoundedBoxShapeInfo::LineLeft(
1135 const nscoord aBStart, const nscoord aBEnd) const {
1136 if (mShapeMargin == 0) {
1137 if (!mRadii) {
1138 return mRect.x;
1139 }
1140
1141 nscoord lineLeftDiff = ComputeEllipseLineInterceptDiff(
1142 mRect.y, mRect.YMost(), mRadii[eCornerTopLeftX],
1143 mRadii[eCornerTopLeftY], mRadii[eCornerBottomLeftX],
1144 mRadii[eCornerBottomLeftY], aBStart, aBEnd);
1145 return mRect.x + lineLeftDiff;
1146 }
1147
1148 MOZ_ASSERT(mLogicalTopLeftCorner && mLogicalBottomLeftCorner,
1149 "If we have positive shape-margin, we should have corners.");
1150
1151 // Determine if aBEnd is within our top corner.
1152 if (aBEnd < mLogicalTopLeftCorner->BEnd()) {
1153 return mLogicalTopLeftCorner->LineLeft(aBStart, aBEnd);
1154 }
1155
1156 // Determine if aBStart is within our bottom corner.
1157 if (aBStart >= mLogicalBottomLeftCorner->BStart()) {
1158 return mLogicalBottomLeftCorner->LineLeft(aBStart, aBEnd);
1159 }
1160
1161 // Either aBStart or aBEnd or both are within the flat part of our left
1162 // edge. Because we've already inflated our mRect to encompass our
1163 // mShapeMargin, we can just return the edge.
1164 return mRect.X();
1165 }
1166
LineRight(const nscoord aBStart,const nscoord aBEnd) const1167 nscoord nsFloatManager::RoundedBoxShapeInfo::LineRight(
1168 const nscoord aBStart, const nscoord aBEnd) const {
1169 if (mShapeMargin == 0) {
1170 if (!mRadii) {
1171 return mRect.XMost();
1172 }
1173
1174 nscoord lineRightDiff = ComputeEllipseLineInterceptDiff(
1175 mRect.y, mRect.YMost(), mRadii[eCornerTopRightX],
1176 mRadii[eCornerTopRightY], mRadii[eCornerBottomRightX],
1177 mRadii[eCornerBottomRightY], aBStart, aBEnd);
1178 return mRect.XMost() - lineRightDiff;
1179 }
1180
1181 MOZ_ASSERT(mLogicalTopRightCorner && mLogicalBottomRightCorner,
1182 "If we have positive shape-margin, we should have corners.");
1183
1184 // Determine if aBEnd is within our top corner.
1185 if (aBEnd < mLogicalTopRightCorner->BEnd()) {
1186 return mLogicalTopRightCorner->LineRight(aBStart, aBEnd);
1187 }
1188
1189 // Determine if aBStart is within our bottom corner.
1190 if (aBStart >= mLogicalBottomRightCorner->BStart()) {
1191 return mLogicalBottomRightCorner->LineRight(aBStart, aBEnd);
1192 }
1193
1194 // Either aBStart or aBEnd or both are within the flat part of our right
1195 // edge. Because we've already inflated our mRect to encompass our
1196 // mShapeMargin, we can just return the edge.
1197 return mRect.XMost();
1198 }
1199
1200 /////////////////////////////////////////////////////////////////////////////
1201 // PolygonShapeInfo
1202 //
1203 // Implements shape-outside: polygon().
1204 //
1205 class nsFloatManager::PolygonShapeInfo final
1206 : public nsFloatManager::ShapeInfo {
1207 public:
1208 explicit PolygonShapeInfo(nsTArray<nsPoint>&& aVertices);
1209 PolygonShapeInfo(nsTArray<nsPoint>&& aVertices, nscoord aShapeMargin,
1210 int32_t aAppUnitsPerDevPixel, const nsRect& aMarginRect);
1211
1212 nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
1213 nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
BStart() const1214 nscoord BStart() const override { return mBStart; }
BEnd() const1215 nscoord BEnd() const override { return mBEnd; }
IsEmpty() const1216 bool IsEmpty() const override {
1217 // A PolygonShapeInfo is never empty, because the parser prevents us from
1218 // creating a shape with no vertices. If we only have 1 vertex, the
1219 // shape acts like a point. With 2 non-coincident vertices, the shape
1220 // acts like a line.
1221 return false;
1222 }
MayNarrowInBlockDirection() const1223 bool MayNarrowInBlockDirection() const override { return true; }
1224
1225 void Translate(nscoord aLineLeft, nscoord aBlockStart) override;
1226
1227 private:
1228 // Helper method for determining the mBStart and mBEnd based on the
1229 // vertices' y extent.
1230 void ComputeExtent();
1231
1232 // Helper method for implementing LineLeft() and LineRight().
1233 nscoord ComputeLineIntercept(
1234 const nscoord aBStart, const nscoord aBEnd,
1235 nscoord (*aCompareOp)(std::initializer_list<nscoord>),
1236 const nscoord aLineInterceptInitialValue) const;
1237
1238 // Given a horizontal line y, and two points p1 and p2 forming a line
1239 // segment L. Solve x for the intersection of y and L. This method
1240 // assumes y and L do intersect, and L is *not* horizontal.
1241 static nscoord XInterceptAtY(const nscoord aY, const nsPoint& aP1,
1242 const nsPoint& aP2);
1243
1244 // The vertices of the polygon in the float manager's coordinate space.
1245 nsTArray<nsPoint> mVertices;
1246
1247 // An interval is slice of the float area defined by this PolygonShapeInfo.
1248 // These are only generated and used in float area calculations for
1249 // shape-margin > 0. Each interval is a rectangle that is one device pixel
1250 // deep in the block axis. The values are stored as block edges in the y
1251 // coordinates, and inline edges as the x coordinates.
1252
1253 // The intervals are stored in ascending order on y.
1254 nsTArray<nsRect> mIntervals;
1255
1256 // Computed block start and block end value of the polygon shape. These
1257 // initial values are set to correct values in ComputeExtent(), which is
1258 // called from all constructors. Afterwards, mBStart is guaranteed to be
1259 // less than or equal to mBEnd.
1260 nscoord mBStart = nscoord_MAX;
1261 nscoord mBEnd = nscoord_MIN;
1262 };
1263
PolygonShapeInfo(nsTArray<nsPoint> && aVertices)1264 nsFloatManager::PolygonShapeInfo::PolygonShapeInfo(
1265 nsTArray<nsPoint>&& aVertices)
1266 : mVertices(std::move(aVertices)) {
1267 ComputeExtent();
1268 }
1269
PolygonShapeInfo(nsTArray<nsPoint> && aVertices,nscoord aShapeMargin,int32_t aAppUnitsPerDevPixel,const nsRect & aMarginRect)1270 nsFloatManager::PolygonShapeInfo::PolygonShapeInfo(
1271 nsTArray<nsPoint>&& aVertices, nscoord aShapeMargin,
1272 int32_t aAppUnitsPerDevPixel, const nsRect& aMarginRect)
1273 : mVertices(std::move(aVertices)) {
1274 MOZ_ASSERT(aShapeMargin > 0,
1275 "This constructor should only be used for a "
1276 "polygon with a positive shape-margin.");
1277
1278 ComputeExtent();
1279
1280 // With a positive aShapeMargin, we have to calculate a distance
1281 // field from the opaque pixels, then build intervals based on
1282 // them being within aShapeMargin distance to an opaque pixel.
1283
1284 // Roughly: for each pixel in the margin box, we need to determine the
1285 // distance to the nearest opaque image-pixel. If that distance is less
1286 // than aShapeMargin, we consider this margin-box pixel as being part of
1287 // the float area.
1288
1289 // Computing the distance field is a two-pass O(n) operation.
1290 // We use a chamfer 5-7-11 5x5 matrix to compute minimum distance
1291 // to an opaque pixel. This integer math computation is reasonably
1292 // close to the true Euclidean distance. The distances will be
1293 // approximately 5x the true distance, quantized in integer units.
1294 // The 5x is factored away in the comparison used in the final
1295 // pass which builds the intervals.
1296 dfType usedMargin5X =
1297 CalcUsedShapeMargin5X(aShapeMargin, aAppUnitsPerDevPixel);
1298
1299 // Allocate our distance field. The distance field has to cover
1300 // the entire aMarginRect, since aShapeMargin could bleed into it.
1301 // Conveniently, our vertices have been converted into this same space,
1302 // so if we cover the aMarginRect, we cover all the vertices.
1303 const LayoutDeviceIntSize marginRectDevPixels =
1304 LayoutDevicePixel::FromAppUnitsRounded(aMarginRect.Size(),
1305 aAppUnitsPerDevPixel);
1306
1307 // Since our distance field is computed with a 5x5 neighborhood,
1308 // we need to expand our distance field by a further 4 pixels in
1309 // both axes, 2 on the leading edge and 2 on the trailing edge.
1310 // We call this edge area the "expanded region".
1311 static const uint32_t kiExpansionPerSide = 2;
1312 static const uint32_t kbExpansionPerSide = 2;
1313
1314 // Clamp the size of our distance field sizes to prevent multiplication
1315 // overflow.
1316 static const uint32_t DF_SIDE_MAX =
1317 floor(sqrt((double)(std::numeric_limits<int32_t>::max())));
1318
1319 // Clamp the margin plus 2X the expansion values between expansion + 1 and
1320 // DF_SIDE_MAX. This ensures that the distance field allocation doesn't
1321 // overflow during multiplication, and the reverse iteration doesn't
1322 // underflow.
1323 const uint32_t iSize =
1324 std::max(std::min(marginRectDevPixels.width + (kiExpansionPerSide * 2),
1325 DF_SIDE_MAX),
1326 kiExpansionPerSide + 1);
1327 const uint32_t bSize =
1328 std::max(std::min(marginRectDevPixels.height + (kbExpansionPerSide * 2),
1329 DF_SIDE_MAX),
1330 kbExpansionPerSide + 1);
1331
1332 // Since the margin-box size is CSS controlled, and large values will
1333 // generate large iSize and bSize values, we do a fallible allocation for
1334 // the distance field. If allocation fails, we early exit and layout will
1335 // be wrong, but we'll avoid aborting from OOM.
1336 auto df = MakeUniqueFallible<dfType[]>(iSize * bSize);
1337 if (!df) {
1338 // Without a distance field, we can't reason about the float area.
1339 return;
1340 }
1341
1342 // First pass setting distance field, starting at top-left, three cases:
1343 // 1) Expanded region pixel: set to MAX_MARGIN_5X.
1344 // 2) Pixel within the polygon: set to 0.
1345 // 3) Other pixel: set to minimum backward-looking neighborhood
1346 // distance value, computed with 5-7-11 chamfer.
1347
1348 for (uint32_t b = 0; b < bSize; ++b) {
1349 // Find the left and right i intercepts of the polygon edge for this
1350 // block row, and adjust them to compensate for the expansion of the
1351 // inline dimension. If we're in the expanded region, or if we're using
1352 // a b that's less than the bStart of the polygon, the intercepts are
1353 // the nscoord min and max limits.
1354 nscoord bInAppUnits = (b - kbExpansionPerSide) * aAppUnitsPerDevPixel;
1355 bool bIsInExpandedRegion(b < kbExpansionPerSide ||
1356 b >= bSize - kbExpansionPerSide);
1357
1358 // We now figure out the i values that correspond to the left edge and
1359 // the right edge of the polygon at one-dev-pixel-thick strip of b. We
1360 // have a ComputeLineIntercept function that takes and returns app unit
1361 // coordinates in the space of aMarginRect. So to pass in b values, we
1362 // first have to add the aMarginRect.y value. And for the values that we
1363 // get out, we have to subtract away the aMarginRect.x value before
1364 // converting the app units to dev pixels.
1365 nscoord bInAppUnitsMarginRect = bInAppUnits + aMarginRect.y;
1366 bool bIsLessThanPolygonBStart(bInAppUnitsMarginRect < mBStart);
1367 bool bIsMoreThanPolygonBEnd(bInAppUnitsMarginRect > mBEnd);
1368
1369 const int32_t iLeftEdge =
1370 (bIsInExpandedRegion || bIsLessThanPolygonBStart ||
1371 bIsMoreThanPolygonBEnd)
1372 ? nscoord_MAX
1373 : kiExpansionPerSide +
1374 NSAppUnitsToIntPixels(
1375 ComputeLineIntercept(
1376 bInAppUnitsMarginRect,
1377 bInAppUnitsMarginRect + aAppUnitsPerDevPixel,
1378 std::min<nscoord>, nscoord_MAX) -
1379 aMarginRect.x,
1380 aAppUnitsPerDevPixel);
1381
1382 const int32_t iRightEdge =
1383 (bIsInExpandedRegion || bIsLessThanPolygonBStart ||
1384 bIsMoreThanPolygonBEnd)
1385 ? nscoord_MIN
1386 : kiExpansionPerSide +
1387 NSAppUnitsToIntPixels(
1388 ComputeLineIntercept(
1389 bInAppUnitsMarginRect,
1390 bInAppUnitsMarginRect + aAppUnitsPerDevPixel,
1391 std::max<nscoord>, nscoord_MIN) -
1392 aMarginRect.x,
1393 aAppUnitsPerDevPixel);
1394
1395 for (uint32_t i = 0; i < iSize; ++i) {
1396 const uint32_t index = i + b * iSize;
1397 MOZ_ASSERT(index < (iSize * bSize),
1398 "Our distance field index should be in-bounds.");
1399
1400 // Handle our three cases, in order.
1401 if (i < kiExpansionPerSide || i >= iSize - kiExpansionPerSide ||
1402 bIsInExpandedRegion) {
1403 // Case 1: Expanded pixel.
1404 df[index] = MAX_MARGIN_5X;
1405 } else if ((int32_t)i >= iLeftEdge && (int32_t)i <= iRightEdge) {
1406 // Case 2: Polygon pixel, either inside or just adjacent to the right
1407 // edge. We need this special distinction to detect a space between
1408 // edges that is less than one dev pixel.
1409 df[index] = (int32_t)i < iRightEdge ? 0 : 5;
1410 } else {
1411 // Case 3: Other pixel.
1412
1413 // Backward-looking neighborhood distance from target pixel X
1414 // with chamfer 5-7-11 looks like:
1415 //
1416 // +--+--+--+--+--+
1417 // | |11| |11| |
1418 // +--+--+--+--+--+
1419 // |11| 7| 5| 7|11|
1420 // +--+--+--+--+--+
1421 // | | 5| X| | |
1422 // +--+--+--+--+--+
1423 //
1424 // X should be set to the minimum of MAX_MARGIN_5X and the
1425 // values of all of the numbered neighbors summed with the
1426 // value in that chamfer cell.
1427 MOZ_ASSERT(index - (iSize * 2) - 1 < (iSize * bSize) &&
1428 index - iSize - 2 < (iSize * bSize),
1429 "Our distance field most extreme indices should be "
1430 "in-bounds.");
1431
1432 // clang-format off
1433 df[index] = std::min<dfType>(MAX_MARGIN_5X,
1434 std::min<dfType>(df[index - (iSize * 2) - 1] + 11,
1435 std::min<dfType>(df[index - (iSize * 2) + 1] + 11,
1436 std::min<dfType>(df[index - iSize - 2] + 11,
1437 std::min<dfType>(df[index - iSize - 1] + 7,
1438 std::min<dfType>(df[index - iSize] + 5,
1439 std::min<dfType>(df[index - iSize + 1] + 7,
1440 std::min<dfType>(df[index - iSize + 2] + 11,
1441 df[index - 1] + 5))))))));
1442 // clang-format on
1443 }
1444 }
1445 }
1446
1447 // Okay, time for the second pass. This pass is in reverse order from
1448 // the first pass. All of our opaque pixels have been set to 0, and all
1449 // of our expanded region pixels have been set to MAX_MARGIN_5X. Other
1450 // pixels have been set to some value between those two (inclusive) but
1451 // this hasn't yet taken into account the neighbors that were processed
1452 // after them in the first pass. This time we reverse iterate so we can
1453 // apply the forward-looking chamfer.
1454
1455 // This time, we constrain our outer and inner loop to ignore the
1456 // expanded region pixels. For each pixel we iterate, we set the df value
1457 // to the minimum forward-looking neighborhood distance value, computed
1458 // with a 5-7-11 chamfer. We also check each df value against the
1459 // usedMargin5X threshold, and use that to set the iMin and iMax values
1460 // for the interval we'll create for that block axis value (b).
1461
1462 // At the end of each row, if any of the other pixels had a value less
1463 // than usedMargin5X, we create an interval.
1464 for (uint32_t b = bSize - kbExpansionPerSide - 1; b >= kbExpansionPerSide;
1465 --b) {
1466 // iMin tracks the first df pixel and iMax the last df pixel whose
1467 // df[] value is less than usedMargin5X. Set iMin and iMax in
1468 // preparation for this row or column.
1469 int32_t iMin = iSize;
1470 int32_t iMax = -1;
1471
1472 for (uint32_t i = iSize - kiExpansionPerSide - 1; i >= kiExpansionPerSide;
1473 --i) {
1474 const uint32_t index = i + b * iSize;
1475 MOZ_ASSERT(index < (iSize * bSize),
1476 "Our distance field index should be in-bounds.");
1477
1478 // Only apply the chamfer calculation if the df value is not
1479 // already 0, since the chamfer can only reduce the value.
1480 if (df[index]) {
1481 // Forward-looking neighborhood distance from target pixel X
1482 // with chamfer 5-7-11 looks like:
1483 //
1484 // +--+--+--+--+--+
1485 // | | | X| 5| |
1486 // +--+--+--+--+--+
1487 // |11| 7| 5| 7|11|
1488 // +--+--+--+--+--+
1489 // | |11| |11| |
1490 // +--+--+--+--+--+
1491 //
1492 // X should be set to the minimum of its current value and
1493 // the values of all of the numbered neighbors summed with
1494 // the value in that chamfer cell.
1495 MOZ_ASSERT(index + (iSize * 2) + 1 < (iSize * bSize) &&
1496 index + iSize + 2 < (iSize * bSize),
1497 "Our distance field most extreme indices should be "
1498 "in-bounds.");
1499
1500 // clang-format off
1501 df[index] = std::min<dfType>(df[index],
1502 std::min<dfType>(df[index + (iSize * 2) + 1] + 11,
1503 std::min<dfType>(df[index + (iSize * 2) - 1] + 11,
1504 std::min<dfType>(df[index + iSize + 2] + 11,
1505 std::min<dfType>(df[index + iSize + 1] + 7,
1506 std::min<dfType>(df[index + iSize] + 5,
1507 std::min<dfType>(df[index + iSize - 1] + 7,
1508 std::min<dfType>(df[index + iSize - 2] + 11,
1509 df[index + 1] + 5))))))));
1510 // clang-format on
1511 }
1512
1513 // Finally, we can check the df value and see if it's less than
1514 // or equal to the usedMargin5X value.
1515 if (df[index] <= usedMargin5X) {
1516 if (iMax == -1) {
1517 iMax = i;
1518 }
1519 MOZ_ASSERT(iMin > (int32_t)i);
1520 iMin = i;
1521 }
1522 }
1523
1524 if (iMax != -1) {
1525 // Our interval values, iMin, iMax, and b are all calculated from
1526 // the expanded region, which is based on the margin rect. To create
1527 // our interval, we have to subtract kiExpansionPerSide from iMin and
1528 // iMax, and subtract kbExpansionPerSide from b to account for the
1529 // expanded region edges. This produces coords that are relative to
1530 // our margin-rect.
1531
1532 // Origin for this interval is at the aMarginRect origin, adjusted in
1533 // the block direction by b in app units, and in the inline direction
1534 // by iMin in app units.
1535 nsPoint origin(
1536 aMarginRect.x + (iMin - kiExpansionPerSide) * aAppUnitsPerDevPixel,
1537 aMarginRect.y + (b - kbExpansionPerSide) * aAppUnitsPerDevPixel);
1538
1539 // Size is the difference in iMax and iMin, plus 1 (to account for the
1540 // whole pixel) dev pixels, by 1 block dev pixel. We don't bother
1541 // subtracting kiExpansionPerSide from iMin and iMax in this case
1542 // because we only care about the distance between them. We convert
1543 // everything to app units.
1544 nsSize size((iMax - iMin + 1) * aAppUnitsPerDevPixel,
1545 aAppUnitsPerDevPixel);
1546
1547 mIntervals.AppendElement(nsRect(origin, size));
1548 }
1549 }
1550
1551 // Reverse the intervals keep the array sorted on the block direction.
1552 mIntervals.Reverse();
1553
1554 // Adjust our extents by aShapeMargin. This may cause overflow of some
1555 // kind if aShapeMargin is large, so we do some clamping to maintain the
1556 // invariant mBStart <= mBEnd.
1557 mBStart = std::min(mBStart, mBStart - aShapeMargin);
1558 mBEnd = std::max(mBEnd, mBEnd + aShapeMargin);
1559 }
1560
LineLeft(const nscoord aBStart,const nscoord aBEnd) const1561 nscoord nsFloatManager::PolygonShapeInfo::LineLeft(const nscoord aBStart,
1562 const nscoord aBEnd) const {
1563 // Use intervals if we have them.
1564 if (!mIntervals.IsEmpty()) {
1565 return LineEdge(mIntervals, aBStart, aBEnd, true);
1566 }
1567
1568 // We want the line-left-most inline-axis coordinate where the
1569 // (block-axis) aBStart/aBEnd band crosses a line segment of the polygon.
1570 // To get that, we start as line-right as possible (at nscoord_MAX). Then
1571 // we iterate each line segment to compute its intersection point with the
1572 // band (if any) and using std::min() successively to get the smallest
1573 // inline-coordinates among those intersection points.
1574 //
1575 // Note: std::min<nscoord> means the function std::min() with template
1576 // parameter nscoord, not the minimum value of nscoord.
1577 return ComputeLineIntercept(aBStart, aBEnd, std::min<nscoord>, nscoord_MAX);
1578 }
1579
LineRight(const nscoord aBStart,const nscoord aBEnd) const1580 nscoord nsFloatManager::PolygonShapeInfo::LineRight(const nscoord aBStart,
1581 const nscoord aBEnd) const {
1582 // Use intervals if we have them.
1583 if (!mIntervals.IsEmpty()) {
1584 return LineEdge(mIntervals, aBStart, aBEnd, false);
1585 }
1586
1587 // Similar to LineLeft(). Though here, we want the line-right-most
1588 // inline-axis coordinate, so we instead start at nscoord_MIN and use
1589 // std::max() to get the biggest inline-coordinate among those
1590 // intersection points.
1591 return ComputeLineIntercept(aBStart, aBEnd, std::max<nscoord>, nscoord_MIN);
1592 }
1593
ComputeExtent()1594 void nsFloatManager::PolygonShapeInfo::ComputeExtent() {
1595 // mBStart and mBEnd are the lower and the upper bounds of all the
1596 // vertex.y, respectively. The vertex.y is actually on the block-axis of
1597 // the float manager's writing mode.
1598 for (const nsPoint& vertex : mVertices) {
1599 mBStart = std::min(mBStart, vertex.y);
1600 mBEnd = std::max(mBEnd, vertex.y);
1601 }
1602
1603 MOZ_ASSERT(mBStart <= mBEnd,
1604 "Start of float area should be less than "
1605 "or equal to the end.");
1606 }
1607
ComputeLineIntercept(const nscoord aBStart,const nscoord aBEnd,nscoord (* aCompareOp)(std::initializer_list<nscoord>),const nscoord aLineInterceptInitialValue) const1608 nscoord nsFloatManager::PolygonShapeInfo::ComputeLineIntercept(
1609 const nscoord aBStart, const nscoord aBEnd,
1610 nscoord (*aCompareOp)(std::initializer_list<nscoord>),
1611 const nscoord aLineInterceptInitialValue) const {
1612 MOZ_ASSERT(aBStart <= aBEnd,
1613 "The band's block start is greater than its block end?");
1614
1615 const size_t len = mVertices.Length();
1616 nscoord lineIntercept = aLineInterceptInitialValue;
1617
1618 // We have some special treatment of horizontal lines between vertices.
1619 // Generally, we can ignore the impact of the horizontal lines since their
1620 // endpoints will be included in the lines preceeding or following them.
1621 // However, it's possible the polygon is entirely a horizontal line,
1622 // possibly built from more than one horizontal segment. In such a case,
1623 // we need to have the horizontal line(s) contribute to the line intercepts.
1624 // We do this by accepting horizontal lines until we find a non-horizontal
1625 // line, after which all further horizontal lines are ignored.
1626 bool canIgnoreHorizontalLines = false;
1627
1628 // Iterate each line segment {p0, p1}, {p1, p2}, ..., {pn, p0}.
1629 for (size_t i = 0; i < len; ++i) {
1630 const nsPoint* smallYVertex = &mVertices[i];
1631 const nsPoint* bigYVertex = &mVertices[(i + 1) % len];
1632
1633 // Swap the two points to satisfy the requirement for calling
1634 // XInterceptAtY.
1635 if (smallYVertex->y > bigYVertex->y) {
1636 std::swap(smallYVertex, bigYVertex);
1637 }
1638
1639 // Generally, we need to ignore line segments that either don't intersect
1640 // the band, or merely touch it. However, if the polygon has no block extent
1641 // (it is a point, or a horizontal line), and the band touches the line
1642 // segment, we let that line segment through.
1643 if ((aBStart >= bigYVertex->y || aBEnd <= smallYVertex->y) &&
1644 !(mBStart == mBEnd && aBStart == bigYVertex->y)) {
1645 // Skip computing the intercept if the band doesn't intersect the
1646 // line segment.
1647 continue;
1648 }
1649
1650 nscoord bStartLineIntercept;
1651 nscoord bEndLineIntercept;
1652
1653 if (smallYVertex->y == bigYVertex->y) {
1654 // The line is horizontal; see if we can ignore it.
1655 if (canIgnoreHorizontalLines) {
1656 continue;
1657 }
1658
1659 // For a horizontal line that we can't ignore, we treat the two x value
1660 // ends as the bStartLineIntercept and bEndLineIntercept. It doesn't
1661 // matter which is applied to which, because they'll both be applied
1662 // to aCompareOp.
1663 bStartLineIntercept = smallYVertex->x;
1664 bEndLineIntercept = bigYVertex->x;
1665 } else {
1666 // This is not a horizontal line. We can now ignore all future
1667 // horizontal lines.
1668 canIgnoreHorizontalLines = true;
1669
1670 bStartLineIntercept =
1671 aBStart <= smallYVertex->y
1672 ? smallYVertex->x
1673 : XInterceptAtY(aBStart, *smallYVertex, *bigYVertex);
1674 bEndLineIntercept =
1675 aBEnd >= bigYVertex->y
1676 ? bigYVertex->x
1677 : XInterceptAtY(aBEnd, *smallYVertex, *bigYVertex);
1678 }
1679
1680 // If either new intercept is more extreme than lineIntercept (per
1681 // aCompareOp), then update lineIntercept to that value.
1682 lineIntercept =
1683 aCompareOp({lineIntercept, bStartLineIntercept, bEndLineIntercept});
1684 }
1685
1686 return lineIntercept;
1687 }
1688
Translate(nscoord aLineLeft,nscoord aBlockStart)1689 void nsFloatManager::PolygonShapeInfo::Translate(nscoord aLineLeft,
1690 nscoord aBlockStart) {
1691 for (nsPoint& vertex : mVertices) {
1692 vertex.MoveBy(aLineLeft, aBlockStart);
1693 }
1694 for (nsRect& interval : mIntervals) {
1695 interval.MoveBy(aLineLeft, aBlockStart);
1696 }
1697 mBStart += aBlockStart;
1698 mBEnd += aBlockStart;
1699 }
1700
1701 /* static */
XInterceptAtY(const nscoord aY,const nsPoint & aP1,const nsPoint & aP2)1702 nscoord nsFloatManager::PolygonShapeInfo::XInterceptAtY(const nscoord aY,
1703 const nsPoint& aP1,
1704 const nsPoint& aP2) {
1705 // Solve for x in the linear equation: x = x1 + (y-y1) * (x2-x1) / (y2-y1),
1706 // where aP1 = (x1, y1) and aP2 = (x2, y2).
1707
1708 MOZ_ASSERT(aP1.y <= aY && aY <= aP2.y,
1709 "This function won't work if the horizontal line at aY and "
1710 "the line segment (aP1, aP2) do not intersect!");
1711
1712 MOZ_ASSERT(aP1.y != aP2.y,
1713 "A horizontal line segment results in dividing by zero error!");
1714
1715 return aP1.x + (aY - aP1.y) * (aP2.x - aP1.x) / (aP2.y - aP1.y);
1716 }
1717
1718 /////////////////////////////////////////////////////////////////////////////
1719 // ImageShapeInfo
1720 //
1721 // Implements shape-outside: <image>
1722 //
1723 class nsFloatManager::ImageShapeInfo final : public nsFloatManager::ShapeInfo {
1724 public:
1725 ImageShapeInfo(uint8_t* aAlphaPixels, int32_t aStride,
1726 const LayoutDeviceIntSize& aImageSize,
1727 int32_t aAppUnitsPerDevPixel, float aShapeImageThreshold,
1728 nscoord aShapeMargin, const nsRect& aContentRect,
1729 const nsRect& aMarginRect, WritingMode aWM,
1730 const nsSize& aContainerSize);
1731
1732 nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
1733 nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
BStart() const1734 nscoord BStart() const override { return mBStart; }
BEnd() const1735 nscoord BEnd() const override { return mBEnd; }
IsEmpty() const1736 bool IsEmpty() const override { return mIntervals.IsEmpty(); }
MayNarrowInBlockDirection() const1737 bool MayNarrowInBlockDirection() const override { return true; }
1738
1739 void Translate(nscoord aLineLeft, nscoord aBlockStart) override;
1740
1741 private:
1742 // An interval is slice of the float area defined by this ImageShapeInfo.
1743 // Each interval is a rectangle that is one pixel deep in the block
1744 // axis. The values are stored as block edges in the y coordinates,
1745 // and inline edges as the x coordinates.
1746
1747 // The intervals are stored in ascending order on y.
1748 nsTArray<nsRect> mIntervals;
1749
1750 nscoord mBStart = nscoord_MAX;
1751 nscoord mBEnd = nscoord_MIN;
1752
1753 // CreateInterval transforms the supplied aIMin and aIMax and aB
1754 // values into an interval that respects the writing mode. An
1755 // aOffsetFromContainer can be provided if the aIMin, aIMax, aB
1756 // values were generated relative to something other than the container
1757 // rect (such as the content rect or margin rect).
1758 void CreateInterval(int32_t aIMin, int32_t aIMax, int32_t aB,
1759 int32_t aAppUnitsPerDevPixel,
1760 const nsPoint& aOffsetFromContainer, WritingMode aWM,
1761 const nsSize& aContainerSize);
1762 };
1763
ImageShapeInfo(uint8_t * aAlphaPixels,int32_t aStride,const LayoutDeviceIntSize & aImageSize,int32_t aAppUnitsPerDevPixel,float aShapeImageThreshold,nscoord aShapeMargin,const nsRect & aContentRect,const nsRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)1764 nsFloatManager::ImageShapeInfo::ImageShapeInfo(
1765 uint8_t* aAlphaPixels, int32_t aStride,
1766 const LayoutDeviceIntSize& aImageSize, int32_t aAppUnitsPerDevPixel,
1767 float aShapeImageThreshold, nscoord aShapeMargin,
1768 const nsRect& aContentRect, const nsRect& aMarginRect, WritingMode aWM,
1769 const nsSize& aContainerSize) {
1770 MOZ_ASSERT(aShapeImageThreshold >= 0.0 && aShapeImageThreshold <= 1.0,
1771 "The computed value of shape-image-threshold is wrong!");
1772
1773 const uint8_t threshold = NSToIntFloor(aShapeImageThreshold * 255);
1774
1775 MOZ_ASSERT(aImageSize.width >= 0 && aImageSize.height >= 0,
1776 "Image size must be non-negative for our math to work.");
1777 const uint32_t w = aImageSize.width;
1778 const uint32_t h = aImageSize.height;
1779
1780 if (aShapeMargin <= 0) {
1781 // Without a positive aShapeMargin, all we have to do is a
1782 // direct threshold comparison of the alpha pixels.
1783 // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
1784
1785 // Scan the pixels in a double loop. For horizontal writing modes, we do
1786 // this row by row, from top to bottom. For vertical writing modes, we do
1787 // column by column, from left to right. We define the two loops
1788 // generically, then figure out the rows and cols within the inner loop.
1789 const uint32_t bSize = aWM.IsVertical() ? w : h;
1790 const uint32_t iSize = aWM.IsVertical() ? h : w;
1791 for (uint32_t b = 0; b < bSize; ++b) {
1792 // iMin and max store the start and end of the float area for the row
1793 // or column represented by this iteration of the outer loop.
1794 int32_t iMin = -1;
1795 int32_t iMax = -1;
1796
1797 for (uint32_t i = 0; i < iSize; ++i) {
1798 const uint32_t col = aWM.IsVertical() ? b : i;
1799 const uint32_t row = aWM.IsVertical() ? i : b;
1800 const uint32_t index = col + row * aStride;
1801
1802 // Determine if the alpha pixel at this row and column has a value
1803 // greater than the threshold. If it does, update our iMin and iMax
1804 // values to track the edges of the float area for this row or column.
1805 // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
1806 const uint8_t alpha = aAlphaPixels[index];
1807 if (alpha > threshold) {
1808 if (iMin == -1) {
1809 iMin = i;
1810 }
1811 MOZ_ASSERT(iMax < (int32_t)i);
1812 iMax = i;
1813 }
1814 }
1815
1816 // At the end of a row or column; did we find something?
1817 if (iMin != -1) {
1818 // We need to supply an offset of the content rect top left, since
1819 // our col and row have been calculated from the content rect,
1820 // instead of the margin rect (against which floats are applied).
1821 CreateInterval(iMin, iMax, b, aAppUnitsPerDevPixel,
1822 aContentRect.TopLeft(), aWM, aContainerSize);
1823 }
1824 }
1825
1826 if (aWM.IsVerticalRL()) {
1827 // vertical-rl or sideways-rl.
1828 // Because we scan the columns from left to right, we need to reverse
1829 // the array so that it's sorted (in ascending order) on the block
1830 // direction.
1831 mIntervals.Reverse();
1832 }
1833 } else {
1834 // With a positive aShapeMargin, we have to calculate a distance
1835 // field from the opaque pixels, then build intervals based on
1836 // them being within aShapeMargin distance to an opaque pixel.
1837
1838 // Roughly: for each pixel in the margin box, we need to determine the
1839 // distance to the nearest opaque image-pixel. If that distance is less
1840 // than aShapeMargin, we consider this margin-box pixel as being part of
1841 // the float area.
1842
1843 // Computing the distance field is a two-pass O(n) operation.
1844 // We use a chamfer 5-7-11 5x5 matrix to compute minimum distance
1845 // to an opaque pixel. This integer math computation is reasonably
1846 // close to the true Euclidean distance. The distances will be
1847 // approximately 5x the true distance, quantized in integer units.
1848 // The 5x is factored away in the comparison used in the final
1849 // pass which builds the intervals.
1850 dfType usedMargin5X =
1851 CalcUsedShapeMargin5X(aShapeMargin, aAppUnitsPerDevPixel);
1852
1853 // Allocate our distance field. The distance field has to cover
1854 // the entire aMarginRect, since aShapeMargin could bleed into it,
1855 // beyond the content rect covered by aAlphaPixels. To make this work,
1856 // we calculate a dfOffset value which is the top left of the content
1857 // rect relative to the margin rect.
1858 nsPoint offsetPoint = aContentRect.TopLeft() - aMarginRect.TopLeft();
1859 LayoutDeviceIntPoint dfOffset = LayoutDevicePixel::FromAppUnitsRounded(
1860 offsetPoint, aAppUnitsPerDevPixel);
1861
1862 // Since our distance field is computed with a 5x5 neighborhood,
1863 // we need to expand our distance field by a further 4 pixels in
1864 // both axes, 2 on the leading edge and 2 on the trailing edge.
1865 // We call this edge area the "expanded region".
1866
1867 // Our expansion amounts need to be the same for our math to work.
1868 static uint32_t kExpansionPerSide = 2;
1869 // Since dfOffset will be used in comparisons against expanded region
1870 // pixel values, it's convenient to add expansion amounts to dfOffset in
1871 // both axes, to simplify comparison math later.
1872 dfOffset.x += kExpansionPerSide;
1873 dfOffset.y += kExpansionPerSide;
1874
1875 // In all these calculations, we purposely ignore aStride, because
1876 // we don't have to replicate the packing that we received in
1877 // aAlphaPixels. When we need to convert from df coordinates to
1878 // alpha coordinates, we do that with math based on row and col.
1879 const LayoutDeviceIntSize marginRectDevPixels =
1880 LayoutDevicePixel::FromAppUnitsRounded(aMarginRect.Size(),
1881 aAppUnitsPerDevPixel);
1882
1883 // Clamp the size of our distance field sizes to prevent multiplication
1884 // overflow.
1885 static const uint32_t DF_SIDE_MAX =
1886 floor(sqrt((double)(std::numeric_limits<int32_t>::max())));
1887
1888 // Clamp the margin plus 2X the expansion values between expansion + 1
1889 // and DF_SIDE_MAX. This ensures that the distance field allocation
1890 // doesn't overflow during multiplication, and the reverse iteration
1891 // doesn't underflow.
1892 const uint32_t wEx =
1893 std::max(std::min(marginRectDevPixels.width + (kExpansionPerSide * 2),
1894 DF_SIDE_MAX),
1895 kExpansionPerSide + 1);
1896 const uint32_t hEx =
1897 std::max(std::min(marginRectDevPixels.height + (kExpansionPerSide * 2),
1898 DF_SIDE_MAX),
1899 kExpansionPerSide + 1);
1900
1901 // Since the margin-box size is CSS controlled, and large values will
1902 // generate large wEx and hEx values, we do a falliable allocation for
1903 // the distance field. If allocation fails, we early exit and layout will
1904 // be wrong, but we'll avoid aborting from OOM.
1905 auto df = MakeUniqueFallible<dfType[]>(wEx * hEx);
1906 if (!df) {
1907 // Without a distance field, we can't reason about the float area.
1908 return;
1909 }
1910
1911 const uint32_t bSize = aWM.IsVertical() ? wEx : hEx;
1912 const uint32_t iSize = aWM.IsVertical() ? hEx : wEx;
1913
1914 // First pass setting distance field, starting at top-left, three cases:
1915 // 1) Expanded region pixel: set to MAX_MARGIN_5X.
1916 // 2) Image pixel with alpha greater than threshold: set to 0.
1917 // 3) Other pixel: set to minimum backward-looking neighborhood
1918 // distance value, computed with 5-7-11 chamfer.
1919
1920 // Scan the pixels in a double loop. For horizontal writing modes, we do
1921 // this row by row, from top to bottom. For vertical writing modes, we do
1922 // column by column, from left to right. We define the two loops
1923 // generically, then figure out the rows and cols within the inner loop.
1924 for (uint32_t b = 0; b < bSize; ++b) {
1925 for (uint32_t i = 0; i < iSize; ++i) {
1926 const uint32_t col = aWM.IsVertical() ? b : i;
1927 const uint32_t row = aWM.IsVertical() ? i : b;
1928 const uint32_t index = col + row * wEx;
1929 MOZ_ASSERT(index < (wEx * hEx),
1930 "Our distance field index should be in-bounds.");
1931
1932 // Handle our three cases, in order.
1933 if (col < kExpansionPerSide || col >= wEx - kExpansionPerSide ||
1934 row < kExpansionPerSide || row >= hEx - kExpansionPerSide) {
1935 // Case 1: Expanded pixel.
1936 df[index] = MAX_MARGIN_5X;
1937 } else if ((int32_t)col >= dfOffset.x &&
1938 (int32_t)col < (dfOffset.x + aImageSize.width) &&
1939 (int32_t)row >= dfOffset.y &&
1940 (int32_t)row < (dfOffset.y + aImageSize.height) &&
1941 aAlphaPixels[col - dfOffset.x +
1942 (row - dfOffset.y) * aStride] > threshold) {
1943 // Case 2: Image pixel that is opaque.
1944 DebugOnly<uint32_t> alphaIndex =
1945 col - dfOffset.x + (row - dfOffset.y) * aStride;
1946 MOZ_ASSERT(alphaIndex < (aStride * h),
1947 "Our aAlphaPixels index should be in-bounds.");
1948
1949 df[index] = 0;
1950 } else {
1951 // Case 3: Other pixel.
1952 if (aWM.IsVertical()) {
1953 // Column-by-column, starting at the left, each column
1954 // top-to-bottom.
1955 // Backward-looking neighborhood distance from target pixel X
1956 // with chamfer 5-7-11 looks like:
1957 //
1958 // +--+--+--+
1959 // | |11| | | +
1960 // +--+--+--+ | /|
1961 // |11| 7| 5| | / |
1962 // +--+--+--+ | / V
1963 // | | 5| X| |/
1964 // +--+--+--+ +
1965 // |11| 7| |
1966 // +--+--+--+
1967 // | |11| |
1968 // +--+--+--+
1969 //
1970 // X should be set to the minimum of MAX_MARGIN_5X and the
1971 // values of all of the numbered neighbors summed with the
1972 // value in that chamfer cell.
1973 MOZ_ASSERT(index - wEx - 2 < (iSize * bSize) &&
1974 index + wEx - 2 < (iSize * bSize) &&
1975 index - (wEx * 2) - 1 < (iSize * bSize),
1976 "Our distance field most extreme indices should be "
1977 "in-bounds.");
1978
1979 // clang-format off
1980 df[index] = std::min<dfType>(MAX_MARGIN_5X,
1981 std::min<dfType>(df[index - wEx - 2] + 11,
1982 std::min<dfType>(df[index + wEx - 2] + 11,
1983 std::min<dfType>(df[index - (wEx * 2) - 1] + 11,
1984 std::min<dfType>(df[index - wEx - 1] + 7,
1985 std::min<dfType>(df[index - 1] + 5,
1986 std::min<dfType>(df[index + wEx - 1] + 7,
1987 std::min<dfType>(df[index + (wEx * 2) - 1] + 11,
1988 df[index - wEx] + 5))))))));
1989 // clang-format on
1990 } else {
1991 // Row-by-row, starting at the top, each row left-to-right.
1992 // Backward-looking neighborhood distance from target pixel X
1993 // with chamfer 5-7-11 looks like:
1994 //
1995 // +--+--+--+--+--+
1996 // | |11| |11| | ----+
1997 // +--+--+--+--+--+ /
1998 // |11| 7| 5| 7|11| /
1999 // +--+--+--+--+--+ /
2000 // | | 5| X| | | +-->
2001 // +--+--+--+--+--+
2002 //
2003 // X should be set to the minimum of MAX_MARGIN_5X and the
2004 // values of all of the numbered neighbors summed with the
2005 // value in that chamfer cell.
2006 MOZ_ASSERT(index - (wEx * 2) - 1 < (iSize * bSize) &&
2007 index - wEx - 2 < (iSize * bSize),
2008 "Our distance field most extreme indices should be "
2009 "in-bounds.");
2010
2011 // clang-format off
2012 df[index] = std::min<dfType>(MAX_MARGIN_5X,
2013 std::min<dfType>(df[index - (wEx * 2) - 1] + 11,
2014 std::min<dfType>(df[index - (wEx * 2) + 1] + 11,
2015 std::min<dfType>(df[index - wEx - 2] + 11,
2016 std::min<dfType>(df[index - wEx - 1] + 7,
2017 std::min<dfType>(df[index - wEx] + 5,
2018 std::min<dfType>(df[index - wEx + 1] + 7,
2019 std::min<dfType>(df[index - wEx + 2] + 11,
2020 df[index - 1] + 5))))))));
2021 // clang-format on
2022 }
2023 }
2024 }
2025 }
2026
2027 // Okay, time for the second pass. This pass is in reverse order from
2028 // the first pass. All of our opaque pixels have been set to 0, and all
2029 // of our expanded region pixels have been set to MAX_MARGIN_5X. Other
2030 // pixels have been set to some value between those two (inclusive) but
2031 // this hasn't yet taken into account the neighbors that were processed
2032 // after them in the first pass. This time we reverse iterate so we can
2033 // apply the forward-looking chamfer.
2034
2035 // This time, we constrain our outer and inner loop to ignore the
2036 // expanded region pixels. For each pixel we iterate, we set the df value
2037 // to the minimum forward-looking neighborhood distance value, computed
2038 // with a 5-7-11 chamfer. We also check each df value against the
2039 // usedMargin5X threshold, and use that to set the iMin and iMax values
2040 // for the interval we'll create for that block axis value (b).
2041
2042 // At the end of each row (or column in vertical writing modes),
2043 // if any of the other pixels had a value less than usedMargin5X,
2044 // we create an interval. Note: "bSize - kExpansionPerSide - 1" is the
2045 // index of the final row of pixels before the trailing expanded region.
2046 for (uint32_t b = bSize - kExpansionPerSide - 1; b >= kExpansionPerSide;
2047 --b) {
2048 // iMin tracks the first df pixel and iMax the last df pixel whose
2049 // df[] value is less than usedMargin5X. Set iMin and iMax in
2050 // preparation for this row or column.
2051 int32_t iMin = iSize;
2052 int32_t iMax = -1;
2053
2054 // Note: "iSize - kExpansionPerSide - 1" is the index of the final row
2055 // of pixels before the trailing expanded region.
2056 for (uint32_t i = iSize - kExpansionPerSide - 1; i >= kExpansionPerSide;
2057 --i) {
2058 const uint32_t col = aWM.IsVertical() ? b : i;
2059 const uint32_t row = aWM.IsVertical() ? i : b;
2060 const uint32_t index = col + row * wEx;
2061 MOZ_ASSERT(index < (wEx * hEx),
2062 "Our distance field index should be in-bounds.");
2063
2064 // Only apply the chamfer calculation if the df value is not
2065 // already 0, since the chamfer can only reduce the value.
2066 if (df[index]) {
2067 if (aWM.IsVertical()) {
2068 // Column-by-column, starting at the right, each column
2069 // bottom-to-top.
2070 // Forward-looking neighborhood distance from target pixel X
2071 // with chamfer 5-7-11 looks like:
2072 //
2073 // +--+--+--+
2074 // | |11| | +
2075 // +--+--+--+ /|
2076 // | | 7|11| A / |
2077 // +--+--+--+ | / |
2078 // | X| 5| | |/ |
2079 // +--+--+--+ + |
2080 // | 5| 7|11|
2081 // +--+--+--+
2082 // | |11| |
2083 // +--+--+--+
2084 //
2085 // X should be set to the minimum of its current value and
2086 // the values of all of the numbered neighbors summed with
2087 // the value in that chamfer cell.
2088 MOZ_ASSERT(index + wEx + 2 < (wEx * hEx) &&
2089 index + (wEx * 2) + 1 < (wEx * hEx) &&
2090 index - (wEx * 2) + 1 < (wEx * hEx),
2091 "Our distance field most extreme indices should be "
2092 "in-bounds.");
2093
2094 // clang-format off
2095 df[index] = std::min<dfType>(df[index],
2096 std::min<dfType>(df[index + wEx + 2] + 11,
2097 std::min<dfType>(df[index - wEx + 2] + 11,
2098 std::min<dfType>(df[index + (wEx * 2) + 1] + 11,
2099 std::min<dfType>(df[index + wEx + 1] + 7,
2100 std::min<dfType>(df[index + 1] + 5,
2101 std::min<dfType>(df[index - wEx + 1] + 7,
2102 std::min<dfType>(df[index - (wEx * 2) + 1] + 11,
2103 df[index + wEx] + 5))))))));
2104 // clang-format on
2105 } else {
2106 // Row-by-row, starting at the bottom, each row right-to-left.
2107 // Forward-looking neighborhood distance from target pixel X
2108 // with chamfer 5-7-11 looks like:
2109 //
2110 // +--+--+--+--+--+
2111 // | | | X| 5| | <--+
2112 // +--+--+--+--+--+ /
2113 // |11| 7| 5| 7|11| /
2114 // +--+--+--+--+--+ /
2115 // | |11| |11| | +----
2116 // +--+--+--+--+--+
2117 //
2118 // X should be set to the minimum of its current value and
2119 // the values of all of the numbered neighbors summed with
2120 // the value in that chamfer cell.
2121 MOZ_ASSERT(index + (wEx * 2) + 1 < (wEx * hEx) &&
2122 index + wEx + 2 < (wEx * hEx),
2123 "Our distance field most extreme indices should be "
2124 "in-bounds.");
2125
2126 // clang-format off
2127 df[index] = std::min<dfType>(df[index],
2128 std::min<dfType>(df[index + (wEx * 2) + 1] + 11,
2129 std::min<dfType>(df[index + (wEx * 2) - 1] + 11,
2130 std::min<dfType>(df[index + wEx + 2] + 11,
2131 std::min<dfType>(df[index + wEx + 1] + 7,
2132 std::min<dfType>(df[index + wEx] + 5,
2133 std::min<dfType>(df[index + wEx - 1] + 7,
2134 std::min<dfType>(df[index + wEx - 2] + 11,
2135 df[index + 1] + 5))))))));
2136 // clang-format on
2137 }
2138 }
2139
2140 // Finally, we can check the df value and see if it's less than
2141 // or equal to the usedMargin5X value.
2142 if (df[index] <= usedMargin5X) {
2143 if (iMax == -1) {
2144 iMax = i;
2145 }
2146 MOZ_ASSERT(iMin > (int32_t)i);
2147 iMin = i;
2148 }
2149 }
2150
2151 if (iMax != -1) {
2152 // Our interval values, iMin, iMax, and b are all calculated from
2153 // the expanded region, which is based on the margin rect. To create
2154 // our interval, we have to subtract kExpansionPerSide from (iMin,
2155 // iMax, and b) to account for the expanded region edges. This
2156 // produces coords that are relative to our margin-rect, so we pass
2157 // in aMarginRect.TopLeft() to make CreateInterval convert to our
2158 // container's coordinate space.
2159 CreateInterval(iMin - kExpansionPerSide, iMax - kExpansionPerSide,
2160 b - kExpansionPerSide, aAppUnitsPerDevPixel,
2161 aMarginRect.TopLeft(), aWM, aContainerSize);
2162 }
2163 }
2164
2165 if (!aWM.IsVerticalRL()) {
2166 // Anything other than vertical-rl or sideways-rl.
2167 // Because we assembled our intervals on the bottom-up pass,
2168 // they are reversed for most writing modes. Reverse them to
2169 // keep the array sorted on the block direction.
2170 mIntervals.Reverse();
2171 }
2172 }
2173
2174 if (!mIntervals.IsEmpty()) {
2175 mBStart = mIntervals[0].Y();
2176 mBEnd = mIntervals.LastElement().YMost();
2177 }
2178 }
2179
CreateInterval(int32_t aIMin,int32_t aIMax,int32_t aB,int32_t aAppUnitsPerDevPixel,const nsPoint & aOffsetFromContainer,WritingMode aWM,const nsSize & aContainerSize)2180 void nsFloatManager::ImageShapeInfo::CreateInterval(
2181 int32_t aIMin, int32_t aIMax, int32_t aB, int32_t aAppUnitsPerDevPixel,
2182 const nsPoint& aOffsetFromContainer, WritingMode aWM,
2183 const nsSize& aContainerSize) {
2184 // Store an interval as an nsRect with our inline axis values stored in x
2185 // and our block axis values stored in y. The position is dependent on
2186 // the writing mode, but the size is the same for all writing modes.
2187
2188 // Size is the difference in inline axis edges stored as x, and one
2189 // block axis pixel stored as y. For the inline axis, we add 1 to aIMax
2190 // because we want to capture the far edge of the last pixel.
2191 nsSize size(((aIMax + 1) - aIMin) * aAppUnitsPerDevPixel,
2192 aAppUnitsPerDevPixel);
2193
2194 // Since we started our scanning of the image pixels from the top left,
2195 // the interval position starts from the origin of the content rect,
2196 // converted to logical coordinates.
2197 nsPoint origin =
2198 ConvertToFloatLogical(aOffsetFromContainer, aWM, aContainerSize);
2199
2200 // Depending on the writing mode, we now move the origin.
2201 if (aWM.IsVerticalRL()) {
2202 // vertical-rl or sideways-rl.
2203 // These writing modes proceed from the top right, and each interval
2204 // moves in a positive inline direction and negative block direction.
2205 // That means that the intervals will be reversed after all have been
2206 // constructed. We add 1 to aB to capture the end of the block axis pixel.
2207 origin.MoveBy(aIMin * aAppUnitsPerDevPixel,
2208 (aB + 1) * -aAppUnitsPerDevPixel);
2209 } else if (aWM.IsSidewaysLR()) {
2210 // This writing mode proceeds from the bottom left, and each interval
2211 // moves in a negative inline direction and a positive block direction.
2212 // We add 1 to aIMax to capture the end of the inline axis pixel.
2213 origin.MoveBy((aIMax + 1) * -aAppUnitsPerDevPixel,
2214 aB * aAppUnitsPerDevPixel);
2215 } else {
2216 // horizontal-tb or vertical-lr.
2217 // These writing modes proceed from the top left and each interval
2218 // moves in a positive step in both inline and block directions.
2219 origin.MoveBy(aIMin * aAppUnitsPerDevPixel, aB * aAppUnitsPerDevPixel);
2220 }
2221
2222 mIntervals.AppendElement(nsRect(origin, size));
2223 }
2224
LineLeft(const nscoord aBStart,const nscoord aBEnd) const2225 nscoord nsFloatManager::ImageShapeInfo::LineLeft(const nscoord aBStart,
2226 const nscoord aBEnd) const {
2227 return LineEdge(mIntervals, aBStart, aBEnd, true);
2228 }
2229
LineRight(const nscoord aBStart,const nscoord aBEnd) const2230 nscoord nsFloatManager::ImageShapeInfo::LineRight(const nscoord aBStart,
2231 const nscoord aBEnd) const {
2232 return LineEdge(mIntervals, aBStart, aBEnd, false);
2233 }
2234
Translate(nscoord aLineLeft,nscoord aBlockStart)2235 void nsFloatManager::ImageShapeInfo::Translate(nscoord aLineLeft,
2236 nscoord aBlockStart) {
2237 for (nsRect& interval : mIntervals) {
2238 interval.MoveBy(aLineLeft, aBlockStart);
2239 }
2240
2241 mBStart += aBlockStart;
2242 mBEnd += aBlockStart;
2243 }
2244
2245 /////////////////////////////////////////////////////////////////////////////
2246 // FloatInfo
2247
FloatInfo(nsIFrame * aFrame,nscoord aLineLeft,nscoord aBlockStart,const LogicalRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)2248 nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame, nscoord aLineLeft,
2249 nscoord aBlockStart,
2250 const LogicalRect& aMarginRect,
2251 WritingMode aWM,
2252 const nsSize& aContainerSize)
2253 : mFrame(aFrame),
2254 mLeftBEnd(nscoord_MIN),
2255 mRightBEnd(nscoord_MIN),
2256 mRect(ShapeInfo::ConvertToFloatLogical(aMarginRect, aWM, aContainerSize) +
2257 nsPoint(aLineLeft, aBlockStart)) {
2258 MOZ_COUNT_CTOR(nsFloatManager::FloatInfo);
2259 using ShapeOutsideType = StyleShapeOutside::Tag;
2260
2261 if (IsEmpty()) {
2262 // Per spec, a float area defined by a shape is clipped to the float’s
2263 // margin box. Therefore, no need to create a shape info if the float's
2264 // margin box is empty, since a float area can only be smaller than the
2265 // margin box.
2266
2267 // https://drafts.csswg.org/css-shapes/#relation-to-box-model-and-float-behavior
2268 return;
2269 }
2270
2271 const nsStyleDisplay* styleDisplay = mFrame->StyleDisplay();
2272 const auto& shapeOutside = styleDisplay->mShapeOutside;
2273
2274 nscoord shapeMargin = shapeOutside.IsNone()
2275 ? 0
2276 : nsLayoutUtils::ResolveToLength<true>(
2277 styleDisplay->mShapeMargin,
2278 LogicalSize(aWM, aContainerSize).ISize(aWM));
2279
2280 switch (shapeOutside.tag) {
2281 case ShapeOutsideType::None:
2282 // No need to create shape info.
2283 return;
2284
2285 case ShapeOutsideType::Image: {
2286 float shapeImageThreshold = styleDisplay->mShapeImageThreshold;
2287 mShapeInfo = ShapeInfo::CreateImageShape(
2288 shapeOutside.AsImage(), shapeImageThreshold, shapeMargin, mFrame,
2289 aMarginRect, aWM, aContainerSize);
2290 if (!mShapeInfo) {
2291 // Image is not ready, or fails to load, etc.
2292 return;
2293 }
2294
2295 break;
2296 }
2297
2298 case ShapeOutsideType::Box: {
2299 // Initialize <shape-box>'s reference rect.
2300 LogicalRect shapeBoxRect = ShapeInfo::ComputeShapeBoxRect(
2301 shapeOutside.AsBox(), mFrame, aMarginRect, aWM);
2302 mShapeInfo = ShapeInfo::CreateShapeBox(mFrame, shapeMargin, shapeBoxRect,
2303 aWM, aContainerSize);
2304 break;
2305 }
2306
2307 case ShapeOutsideType::Shape: {
2308 const auto& shape = *shapeOutside.AsShape()._0;
2309 // Initialize <shape-box>'s reference rect.
2310 LogicalRect shapeBoxRect = ShapeInfo::ComputeShapeBoxRect(
2311 shapeOutside.AsShape()._1, mFrame, aMarginRect, aWM);
2312 mShapeInfo =
2313 ShapeInfo::CreateBasicShape(shape, shapeMargin, mFrame, shapeBoxRect,
2314 aMarginRect, aWM, aContainerSize);
2315 break;
2316 }
2317 }
2318
2319 MOZ_ASSERT(mShapeInfo,
2320 "All shape-outside values except none should have mShapeInfo!");
2321
2322 // Translate the shape to the same origin as nsFloatManager.
2323 mShapeInfo->Translate(aLineLeft, aBlockStart);
2324 }
2325
2326 #ifdef NS_BUILD_REFCNT_LOGGING
FloatInfo(FloatInfo && aOther)2327 nsFloatManager::FloatInfo::FloatInfo(FloatInfo&& aOther)
2328 : mFrame(std::move(aOther.mFrame)),
2329 mLeftBEnd(std::move(aOther.mLeftBEnd)),
2330 mRightBEnd(std::move(aOther.mRightBEnd)),
2331 mRect(std::move(aOther.mRect)),
2332 mShapeInfo(std::move(aOther.mShapeInfo)) {
2333 MOZ_COUNT_CTOR(nsFloatManager::FloatInfo);
2334 }
2335
~FloatInfo()2336 nsFloatManager::FloatInfo::~FloatInfo() {
2337 MOZ_COUNT_DTOR(nsFloatManager::FloatInfo);
2338 }
2339 #endif
2340
LineLeft(ShapeType aShapeType,const nscoord aBStart,const nscoord aBEnd) const2341 nscoord nsFloatManager::FloatInfo::LineLeft(ShapeType aShapeType,
2342 const nscoord aBStart,
2343 const nscoord aBEnd) const {
2344 if (aShapeType == ShapeType::Margin) {
2345 return LineLeft();
2346 }
2347
2348 MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2349 if (!mShapeInfo) {
2350 return LineLeft();
2351 }
2352 // Clip the flow area to the margin-box because
2353 // https://drafts.csswg.org/css-shapes-1/#relation-to-box-model-and-float-behavior
2354 // says "When a shape is used to define a float area, the shape is clipped
2355 // to the float’s margin box."
2356 return std::max(LineLeft(), mShapeInfo->LineLeft(aBStart, aBEnd));
2357 }
2358
LineRight(ShapeType aShapeType,const nscoord aBStart,const nscoord aBEnd) const2359 nscoord nsFloatManager::FloatInfo::LineRight(ShapeType aShapeType,
2360 const nscoord aBStart,
2361 const nscoord aBEnd) const {
2362 if (aShapeType == ShapeType::Margin) {
2363 return LineRight();
2364 }
2365
2366 MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2367 if (!mShapeInfo) {
2368 return LineRight();
2369 }
2370 // Clip the flow area to the margin-box. See LineLeft().
2371 return std::min(LineRight(), mShapeInfo->LineRight(aBStart, aBEnd));
2372 }
2373
BStart(ShapeType aShapeType) const2374 nscoord nsFloatManager::FloatInfo::BStart(ShapeType aShapeType) const {
2375 if (aShapeType == ShapeType::Margin) {
2376 return BStart();
2377 }
2378
2379 MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2380 if (!mShapeInfo) {
2381 return BStart();
2382 }
2383 // Clip the flow area to the margin-box. See LineLeft().
2384 return std::max(BStart(), mShapeInfo->BStart());
2385 }
2386
BEnd(ShapeType aShapeType) const2387 nscoord nsFloatManager::FloatInfo::BEnd(ShapeType aShapeType) const {
2388 if (aShapeType == ShapeType::Margin) {
2389 return BEnd();
2390 }
2391
2392 MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2393 if (!mShapeInfo) {
2394 return BEnd();
2395 }
2396 // Clip the flow area to the margin-box. See LineLeft().
2397 return std::min(BEnd(), mShapeInfo->BEnd());
2398 }
2399
IsEmpty(ShapeType aShapeType) const2400 bool nsFloatManager::FloatInfo::IsEmpty(ShapeType aShapeType) const {
2401 if (aShapeType == ShapeType::Margin) {
2402 return IsEmpty();
2403 }
2404
2405 MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2406 if (!mShapeInfo) {
2407 return IsEmpty();
2408 }
2409 return mShapeInfo->IsEmpty();
2410 }
2411
MayNarrowInBlockDirection(ShapeType aShapeType) const2412 bool nsFloatManager::FloatInfo::MayNarrowInBlockDirection(
2413 ShapeType aShapeType) const {
2414 // This function mirrors the cases of the three argument versions of
2415 // LineLeft() and LineRight(). This function returns true if and only if
2416 // either of those functions could possibly return "narrower" values with
2417 // increasing aBStart values. "Narrower" means closer to the far end of
2418 // the float shape.
2419 if (aShapeType == ShapeType::Margin) {
2420 return false;
2421 }
2422
2423 MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2424 if (!mShapeInfo) {
2425 return false;
2426 }
2427
2428 return mShapeInfo->MayNarrowInBlockDirection();
2429 }
2430
2431 /////////////////////////////////////////////////////////////////////////////
2432 // ShapeInfo
2433
2434 /* static */
ComputeShapeBoxRect(StyleShapeBox aBox,nsIFrame * const aFrame,const LogicalRect & aMarginRect,WritingMode aWM)2435 LogicalRect nsFloatManager::ShapeInfo::ComputeShapeBoxRect(
2436 StyleShapeBox aBox, nsIFrame* const aFrame, const LogicalRect& aMarginRect,
2437 WritingMode aWM) {
2438 LogicalRect rect = aMarginRect;
2439
2440 switch (aBox) {
2441 case StyleShapeBox::ContentBox:
2442 rect.Deflate(aWM, aFrame->GetLogicalUsedPadding(aWM));
2443 [[fallthrough]];
2444 case StyleShapeBox::PaddingBox:
2445 rect.Deflate(aWM, aFrame->GetLogicalUsedBorder(aWM));
2446 [[fallthrough]];
2447 case StyleShapeBox::BorderBox:
2448 rect.Deflate(aWM, aFrame->GetLogicalUsedMargin(aWM));
2449 break;
2450 case StyleShapeBox::MarginBox:
2451 // Do nothing. rect is already a margin rect.
2452 break;
2453 default:
2454 MOZ_ASSERT_UNREACHABLE("Unknown shape box");
2455 break;
2456 }
2457
2458 return rect;
2459 }
2460
2461 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreateShapeBox(nsIFrame * const aFrame,nscoord aShapeMargin,const LogicalRect & aShapeBoxRect,WritingMode aWM,const nsSize & aContainerSize)2462 nsFloatManager::ShapeInfo::CreateShapeBox(nsIFrame* const aFrame,
2463 nscoord aShapeMargin,
2464 const LogicalRect& aShapeBoxRect,
2465 WritingMode aWM,
2466 const nsSize& aContainerSize) {
2467 nsRect logicalShapeBoxRect =
2468 ConvertToFloatLogical(aShapeBoxRect, aWM, aContainerSize);
2469
2470 // Inflate logicalShapeBoxRect by aShapeMargin.
2471 logicalShapeBoxRect.Inflate(aShapeMargin);
2472
2473 nscoord physicalRadii[8];
2474 bool hasRadii = aFrame->GetShapeBoxBorderRadii(physicalRadii);
2475 if (!hasRadii) {
2476 return MakeUnique<RoundedBoxShapeInfo>(logicalShapeBoxRect,
2477 UniquePtr<nscoord[]>());
2478 }
2479
2480 // Add aShapeMargin to each of the radii.
2481 for (nscoord& r : physicalRadii) {
2482 r += aShapeMargin;
2483 }
2484
2485 return MakeUnique<RoundedBoxShapeInfo>(
2486 logicalShapeBoxRect, ConvertToFloatLogical(physicalRadii, aWM));
2487 }
2488
2489 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreateBasicShape(const StyleBasicShape & aBasicShape,nscoord aShapeMargin,nsIFrame * const aFrame,const LogicalRect & aShapeBoxRect,const LogicalRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)2490 nsFloatManager::ShapeInfo::CreateBasicShape(const StyleBasicShape& aBasicShape,
2491 nscoord aShapeMargin,
2492 nsIFrame* const aFrame,
2493 const LogicalRect& aShapeBoxRect,
2494 const LogicalRect& aMarginRect,
2495 WritingMode aWM,
2496 const nsSize& aContainerSize) {
2497 switch (aBasicShape.tag) {
2498 case StyleBasicShape::Tag::Polygon:
2499 return CreatePolygon(aBasicShape, aShapeMargin, aFrame, aShapeBoxRect,
2500 aMarginRect, aWM, aContainerSize);
2501 case StyleBasicShape::Tag::Circle:
2502 case StyleBasicShape::Tag::Ellipse:
2503 return CreateCircleOrEllipse(aBasicShape, aShapeMargin, aFrame,
2504 aShapeBoxRect, aWM, aContainerSize);
2505 case StyleBasicShape::Tag::Inset:
2506 return CreateInset(aBasicShape, aShapeMargin, aFrame, aShapeBoxRect, aWM,
2507 aContainerSize);
2508 }
2509 return nullptr;
2510 }
2511
2512 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreateInset(const StyleBasicShape & aBasicShape,nscoord aShapeMargin,nsIFrame * aFrame,const LogicalRect & aShapeBoxRect,WritingMode aWM,const nsSize & aContainerSize)2513 nsFloatManager::ShapeInfo::CreateInset(const StyleBasicShape& aBasicShape,
2514 nscoord aShapeMargin, nsIFrame* aFrame,
2515 const LogicalRect& aShapeBoxRect,
2516 WritingMode aWM,
2517 const nsSize& aContainerSize) {
2518 // Use physical coordinates to compute inset() because the top, right,
2519 // bottom and left offsets are physical.
2520 // https://drafts.csswg.org/css-shapes-1/#funcdef-inset
2521 nsRect physicalShapeBoxRect =
2522 aShapeBoxRect.GetPhysicalRect(aWM, aContainerSize);
2523 nsRect insetRect =
2524 ShapeUtils::ComputeInsetRect(aBasicShape, physicalShapeBoxRect);
2525
2526 nsRect logicalInsetRect = ConvertToFloatLogical(
2527 LogicalRect(aWM, insetRect, aContainerSize), aWM, aContainerSize);
2528 nscoord physicalRadii[8];
2529 bool hasRadii = ShapeUtils::ComputeInsetRadii(
2530 aBasicShape, insetRect, physicalShapeBoxRect, physicalRadii);
2531
2532 // With a zero shape-margin, we will be able to use the fast constructor.
2533 if (aShapeMargin == 0) {
2534 if (!hasRadii) {
2535 return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
2536 UniquePtr<nscoord[]>());
2537 }
2538 return MakeUnique<RoundedBoxShapeInfo>(
2539 logicalInsetRect, ConvertToFloatLogical(physicalRadii, aWM));
2540 }
2541
2542 // With a positive shape-margin, we might still be able to use the fast
2543 // constructor. With no radii, we can build a rounded box by inflating
2544 // logicalInsetRect, and supplying aShapeMargin as the radius for all
2545 // corners.
2546 if (!hasRadii) {
2547 logicalInsetRect.Inflate(aShapeMargin);
2548 auto logicalRadii = MakeUnique<nscoord[]>(8);
2549 for (int32_t i = 0; i < 8; ++i) {
2550 logicalRadii[i] = aShapeMargin;
2551 }
2552 return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
2553 std::move(logicalRadii));
2554 }
2555
2556 // If we have radii, and they have balanced/equal corners, we can inflate
2557 // both logicalInsetRect and all the radii and use the fast constructor.
2558 if (RoundedBoxShapeInfo::EachCornerHasBalancedRadii(physicalRadii)) {
2559 logicalInsetRect.Inflate(aShapeMargin);
2560 for (nscoord& r : physicalRadii) {
2561 r += aShapeMargin;
2562 }
2563 return MakeUnique<RoundedBoxShapeInfo>(
2564 logicalInsetRect, ConvertToFloatLogical(physicalRadii, aWM));
2565 }
2566
2567 // With positive shape-margin and elliptical radii, we have to use the
2568 // slow constructor.
2569 nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
2570 int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
2571 return MakeUnique<RoundedBoxShapeInfo>(
2572 logicalInsetRect, ConvertToFloatLogical(physicalRadii, aWM), aShapeMargin,
2573 appUnitsPerDevPixel);
2574 }
2575
2576 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreateCircleOrEllipse(const StyleBasicShape & aBasicShape,nscoord aShapeMargin,nsIFrame * const aFrame,const LogicalRect & aShapeBoxRect,WritingMode aWM,const nsSize & aContainerSize)2577 nsFloatManager::ShapeInfo::CreateCircleOrEllipse(
2578 const StyleBasicShape& aBasicShape, nscoord aShapeMargin,
2579 nsIFrame* const aFrame, const LogicalRect& aShapeBoxRect, WritingMode aWM,
2580 const nsSize& aContainerSize) {
2581 // Use physical coordinates to compute the center of circle() or ellipse()
2582 // since the <position> keywords such as 'left', 'top', etc. are physical.
2583 // https://drafts.csswg.org/css-shapes-1/#funcdef-ellipse
2584 nsRect physicalShapeBoxRect =
2585 aShapeBoxRect.GetPhysicalRect(aWM, aContainerSize);
2586 nsPoint physicalCenter = ShapeUtils::ComputeCircleOrEllipseCenter(
2587 aBasicShape, physicalShapeBoxRect);
2588 nsPoint logicalCenter =
2589 ConvertToFloatLogical(physicalCenter, aWM, aContainerSize);
2590
2591 // Compute the circle or ellipse radii.
2592 nsSize radii;
2593 if (aBasicShape.IsCircle()) {
2594 nscoord radius = ShapeUtils::ComputeCircleRadius(
2595 aBasicShape, physicalCenter, physicalShapeBoxRect);
2596 // Circles can use the three argument, math constructor for
2597 // EllipseShapeInfo.
2598 radii = nsSize(radius, radius);
2599 return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin);
2600 }
2601
2602 MOZ_ASSERT(aBasicShape.IsEllipse());
2603 nsSize physicalRadii = ShapeUtils::ComputeEllipseRadii(
2604 aBasicShape, physicalCenter, physicalShapeBoxRect);
2605 LogicalSize logicalRadii(aWM, physicalRadii);
2606 radii = nsSize(logicalRadii.ISize(aWM), logicalRadii.BSize(aWM));
2607
2608 // If radii are close to the same value, or if aShapeMargin is small
2609 // enough (as specified in css pixels), then we can use the three argument
2610 // constructor for EllipseShapeInfo, which uses math for a more efficient
2611 // method of float area computation.
2612 if (EllipseShapeInfo::ShapeMarginIsNegligible(aShapeMargin) ||
2613 EllipseShapeInfo::RadiiAreRoughlyEqual(radii)) {
2614 return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin);
2615 }
2616
2617 // We have to use the full constructor for EllipseShapeInfo. This
2618 // computes the float area using a rasterization method.
2619 nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
2620 int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
2621 return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin,
2622 appUnitsPerDevPixel);
2623 }
2624
2625 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreatePolygon(const StyleBasicShape & aBasicShape,nscoord aShapeMargin,nsIFrame * const aFrame,const LogicalRect & aShapeBoxRect,const LogicalRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)2626 nsFloatManager::ShapeInfo::CreatePolygon(const StyleBasicShape& aBasicShape,
2627 nscoord aShapeMargin,
2628 nsIFrame* const aFrame,
2629 const LogicalRect& aShapeBoxRect,
2630 const LogicalRect& aMarginRect,
2631 WritingMode aWM,
2632 const nsSize& aContainerSize) {
2633 // Use physical coordinates to compute each (xi, yi) vertex because CSS
2634 // represents them using physical coordinates.
2635 // https://drafts.csswg.org/css-shapes-1/#funcdef-polygon
2636 nsRect physicalShapeBoxRect =
2637 aShapeBoxRect.GetPhysicalRect(aWM, aContainerSize);
2638
2639 // Get physical vertices.
2640 nsTArray<nsPoint> vertices =
2641 ShapeUtils::ComputePolygonVertices(aBasicShape, physicalShapeBoxRect);
2642
2643 // Convert all the physical vertices to logical.
2644 for (nsPoint& vertex : vertices) {
2645 vertex = ConvertToFloatLogical(vertex, aWM, aContainerSize);
2646 }
2647
2648 if (aShapeMargin == 0) {
2649 return MakeUnique<PolygonShapeInfo>(std::move(vertices));
2650 }
2651
2652 nsRect marginRect = ConvertToFloatLogical(aMarginRect, aWM, aContainerSize);
2653
2654 // We have to use the full constructor for PolygonShapeInfo. This
2655 // computes the float area using a rasterization method.
2656 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
2657 return MakeUnique<PolygonShapeInfo>(std::move(vertices), aShapeMargin,
2658 appUnitsPerDevPixel, marginRect);
2659 }
2660
2661 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreateImageShape(const StyleImage & aShapeImage,float aShapeImageThreshold,nscoord aShapeMargin,nsIFrame * const aFrame,const LogicalRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)2662 nsFloatManager::ShapeInfo::CreateImageShape(const StyleImage& aShapeImage,
2663 float aShapeImageThreshold,
2664 nscoord aShapeMargin,
2665 nsIFrame* const aFrame,
2666 const LogicalRect& aMarginRect,
2667 WritingMode aWM,
2668 const nsSize& aContainerSize) {
2669 MOZ_ASSERT(&aShapeImage == &aFrame->StyleDisplay()->mShapeOutside.AsImage(),
2670 "aFrame should be the frame that we got aShapeImage from");
2671
2672 nsImageRenderer imageRenderer(aFrame, &aShapeImage,
2673 nsImageRenderer::FLAG_SYNC_DECODE_IMAGES);
2674
2675 if (!imageRenderer.PrepareImage()) {
2676 // The image is not ready yet. Boost its loading priority since it will
2677 // affect layout.
2678 if (imgRequestProxy* req = aShapeImage.GetImageRequest()) {
2679 req->BoostPriority(imgIRequest::CATEGORY_SIZE_QUERY);
2680 }
2681 return nullptr;
2682 }
2683
2684 nsRect contentRect = aFrame->GetContentRect();
2685
2686 // Create a draw target and draw shape image on it.
2687 nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
2688 int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
2689 LayoutDeviceIntSize contentSizeInDevPixels =
2690 LayoutDeviceIntSize::FromAppUnitsRounded(contentRect.Size(),
2691 appUnitsPerDevPixel);
2692
2693 // Use empty CSSSizeOrRatio to force set the preferred size as the frame's
2694 // content box size.
2695 imageRenderer.SetPreferredSize(CSSSizeOrRatio(), contentRect.Size());
2696
2697 RefPtr<gfx::DrawTarget> drawTarget =
2698 gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
2699 contentSizeInDevPixels.ToUnknownSize(), gfx::SurfaceFormat::A8);
2700 if (!drawTarget) {
2701 return nullptr;
2702 }
2703
2704 RefPtr<gfxContext> context = gfxContext::CreateOrNull(drawTarget);
2705 MOZ_ASSERT(context); // already checked the target above
2706
2707 ImgDrawResult result =
2708 imageRenderer.DrawShapeImage(aFrame->PresContext(), *context);
2709
2710 if (result != ImgDrawResult::SUCCESS) {
2711 return nullptr;
2712 }
2713
2714 // Retrieve the pixel image buffer to create the image shape info.
2715 RefPtr<SourceSurface> sourceSurface = drawTarget->Snapshot();
2716 RefPtr<DataSourceSurface> dataSourceSurface = sourceSurface->GetDataSurface();
2717 DataSourceSurface::ScopedMap map(dataSourceSurface, DataSourceSurface::READ);
2718
2719 if (!map.IsMapped()) {
2720 return nullptr;
2721 }
2722
2723 MOZ_ASSERT(sourceSurface->GetSize() == contentSizeInDevPixels.ToUnknownSize(),
2724 "Who changes the size?");
2725
2726 nsRect marginRect = aMarginRect.GetPhysicalRect(aWM, aContainerSize);
2727
2728 uint8_t* alphaPixels = map.GetData();
2729 int32_t stride = map.GetStride();
2730
2731 // NOTE: ImageShapeInfo constructor does not keep a persistent copy of
2732 // alphaPixels; it's only used during the constructor to compute pixel ranges.
2733 return MakeUnique<ImageShapeInfo>(alphaPixels, stride, contentSizeInDevPixels,
2734 appUnitsPerDevPixel, aShapeImageThreshold,
2735 aShapeMargin, contentRect, marginRect, aWM,
2736 aContainerSize);
2737 }
2738
2739 /* static */
ComputeEllipseLineInterceptDiff(const nscoord aShapeBoxBStart,const nscoord aShapeBoxBEnd,const nscoord aBStartCornerRadiusL,const nscoord aBStartCornerRadiusB,const nscoord aBEndCornerRadiusL,const nscoord aBEndCornerRadiusB,const nscoord aBandBStart,const nscoord aBandBEnd)2740 nscoord nsFloatManager::ShapeInfo::ComputeEllipseLineInterceptDiff(
2741 const nscoord aShapeBoxBStart, const nscoord aShapeBoxBEnd,
2742 const nscoord aBStartCornerRadiusL, const nscoord aBStartCornerRadiusB,
2743 const nscoord aBEndCornerRadiusL, const nscoord aBEndCornerRadiusB,
2744 const nscoord aBandBStart, const nscoord aBandBEnd) {
2745 // An example for the band intersecting with the top right corner of an
2746 // ellipse with writing-mode horizontal-tb.
2747 //
2748 // lineIntercept lineDiff
2749 // | |
2750 // +---------------------------------|-------|-+---- aShapeBoxBStart
2751 // | ##########^ | | |
2752 // | ##############|#### | | |
2753 // +---------#################|######|-------|-+---- aBandBStart
2754 // | ###################|######|## | |
2755 // | aBStartCornerRadiusB |######|### | |
2756 // | ######################|######|##### | |
2757 // +---#######################|<-----------><->^---- aBandBEnd
2758 // | ########################|############## |
2759 // | ########################|############## |---- b
2760 // | #########################|############### |
2761 // | ######################## v<-------------->v
2762 // |###################### aBStartCornerRadiusL|
2763 // |###########################################|
2764 // |###########################################|
2765 // |###########################################|
2766 // |###########################################|
2767 // | ######################################### |
2768 // | ######################################### |
2769 // | ####################################### |
2770 // | ####################################### |
2771 // | ##################################### |
2772 // | ################################### |
2773 // | ############################### |
2774 // | ############################# |
2775 // | ######################### |
2776 // | ################### |
2777 // | ########### |
2778 // +-------------------------------------------+----- aShapeBoxBEnd
2779
2780 NS_ASSERTION(aShapeBoxBStart <= aShapeBoxBEnd, "Bad shape box coordinates!");
2781 NS_ASSERTION(aBandBStart <= aBandBEnd, "Bad band coordinates!");
2782
2783 nscoord lineDiff = 0;
2784
2785 // If the band intersects both the block-start and block-end corners, we
2786 // don't need to enter either branch because the correct lineDiff is 0.
2787 if (aBStartCornerRadiusB > 0 && aBandBEnd >= aShapeBoxBStart &&
2788 aBandBEnd <= aShapeBoxBStart + aBStartCornerRadiusB) {
2789 // The band intersects only the block-start corner.
2790 nscoord b = aBStartCornerRadiusB - (aBandBEnd - aShapeBoxBStart);
2791 nscoord lineIntercept =
2792 XInterceptAtY(b, aBStartCornerRadiusL, aBStartCornerRadiusB);
2793 lineDiff = aBStartCornerRadiusL - lineIntercept;
2794 } else if (aBEndCornerRadiusB > 0 &&
2795 aBandBStart >= aShapeBoxBEnd - aBEndCornerRadiusB &&
2796 aBandBStart <= aShapeBoxBEnd) {
2797 // The band intersects only the block-end corner.
2798 nscoord b = aBEndCornerRadiusB - (aShapeBoxBEnd - aBandBStart);
2799 nscoord lineIntercept =
2800 XInterceptAtY(b, aBEndCornerRadiusL, aBEndCornerRadiusB);
2801 lineDiff = aBEndCornerRadiusL - lineIntercept;
2802 }
2803
2804 return lineDiff;
2805 }
2806
2807 /* static */
XInterceptAtY(const nscoord aY,const nscoord aRadiusX,const nscoord aRadiusY)2808 nscoord nsFloatManager::ShapeInfo::XInterceptAtY(const nscoord aY,
2809 const nscoord aRadiusX,
2810 const nscoord aRadiusY) {
2811 // Solve for x in the ellipse equation (x/radiusX)^2 + (y/radiusY)^2 = 1.
2812 MOZ_ASSERT(aRadiusY > 0);
2813 return aRadiusX * std::sqrt(1 - (aY * aY) / double(aRadiusY * aRadiusY));
2814 }
2815
2816 /* static */
ConvertToFloatLogical(const nsPoint & aPoint,WritingMode aWM,const nsSize & aContainerSize)2817 nsPoint nsFloatManager::ShapeInfo::ConvertToFloatLogical(
2818 const nsPoint& aPoint, WritingMode aWM, const nsSize& aContainerSize) {
2819 LogicalPoint logicalPoint(aWM, aPoint, aContainerSize);
2820 return nsPoint(logicalPoint.LineRelative(aWM, aContainerSize),
2821 logicalPoint.B(aWM));
2822 }
2823
2824 /* static */ UniquePtr<nscoord[]>
ConvertToFloatLogical(const nscoord aRadii[8],WritingMode aWM)2825 nsFloatManager::ShapeInfo::ConvertToFloatLogical(const nscoord aRadii[8],
2826 WritingMode aWM) {
2827 UniquePtr<nscoord[]> logicalRadii(new nscoord[8]);
2828
2829 // Get the physical side for line-left and line-right since border radii
2830 // are on the physical axis.
2831 Side lineLeftSide =
2832 aWM.PhysicalSide(aWM.LogicalSideForLineRelativeDir(eLineRelativeDirLeft));
2833 logicalRadii[eCornerTopLeftX] =
2834 aRadii[SideToHalfCorner(lineLeftSide, true, false)];
2835 logicalRadii[eCornerTopLeftY] =
2836 aRadii[SideToHalfCorner(lineLeftSide, true, true)];
2837 logicalRadii[eCornerBottomLeftX] =
2838 aRadii[SideToHalfCorner(lineLeftSide, false, false)];
2839 logicalRadii[eCornerBottomLeftY] =
2840 aRadii[SideToHalfCorner(lineLeftSide, false, true)];
2841
2842 Side lineRightSide = aWM.PhysicalSide(
2843 aWM.LogicalSideForLineRelativeDir(eLineRelativeDirRight));
2844 logicalRadii[eCornerTopRightX] =
2845 aRadii[SideToHalfCorner(lineRightSide, false, false)];
2846 logicalRadii[eCornerTopRightY] =
2847 aRadii[SideToHalfCorner(lineRightSide, false, true)];
2848 logicalRadii[eCornerBottomRightX] =
2849 aRadii[SideToHalfCorner(lineRightSide, true, false)];
2850 logicalRadii[eCornerBottomRightY] =
2851 aRadii[SideToHalfCorner(lineRightSide, true, true)];
2852
2853 if (aWM.IsLineInverted()) {
2854 // When IsLineInverted() is true, i.e. aWM is vertical-lr,
2855 // line-over/line-under are inverted from block-start/block-end. So the
2856 // relationship reverses between which corner comes first going
2857 // clockwise, and which corner is block-start versus block-end. We need
2858 // to swap the values stored in top and bottom corners.
2859 std::swap(logicalRadii[eCornerTopLeftX], logicalRadii[eCornerBottomLeftX]);
2860 std::swap(logicalRadii[eCornerTopLeftY], logicalRadii[eCornerBottomLeftY]);
2861 std::swap(logicalRadii[eCornerTopRightX],
2862 logicalRadii[eCornerBottomRightX]);
2863 std::swap(logicalRadii[eCornerTopRightY],
2864 logicalRadii[eCornerBottomRightY]);
2865 }
2866
2867 return logicalRadii;
2868 }
2869
2870 /* static */
MinIntervalIndexContainingY(const nsTArray<nsRect> & aIntervals,const nscoord aTargetY)2871 size_t nsFloatManager::ShapeInfo::MinIntervalIndexContainingY(
2872 const nsTArray<nsRect>& aIntervals, const nscoord aTargetY) {
2873 // Perform a binary search to find the minimum index of an interval
2874 // that contains aTargetY. If no such interval exists, return a value
2875 // equal to the number of intervals.
2876 size_t startIdx = 0;
2877 size_t endIdx = aIntervals.Length();
2878 while (startIdx < endIdx) {
2879 size_t midIdx = startIdx + (endIdx - startIdx) / 2;
2880 if (aIntervals[midIdx].ContainsY(aTargetY)) {
2881 return midIdx;
2882 }
2883 nscoord midY = aIntervals[midIdx].Y();
2884 if (midY < aTargetY) {
2885 startIdx = midIdx + 1;
2886 } else {
2887 endIdx = midIdx;
2888 }
2889 }
2890
2891 return endIdx;
2892 }
2893
2894 /* static */
LineEdge(const nsTArray<nsRect> & aIntervals,const nscoord aBStart,const nscoord aBEnd,bool aIsLineLeft)2895 nscoord nsFloatManager::ShapeInfo::LineEdge(const nsTArray<nsRect>& aIntervals,
2896 const nscoord aBStart,
2897 const nscoord aBEnd,
2898 bool aIsLineLeft) {
2899 MOZ_ASSERT(aBStart <= aBEnd,
2900 "The band's block start is greater than its block end?");
2901
2902 // Find all the intervals whose rects overlap the aBStart to
2903 // aBEnd range, and find the most constraining inline edge
2904 // depending on the value of aLeft.
2905
2906 // Since the intervals are stored in block-axis order, we need
2907 // to find the first interval that overlaps aBStart and check
2908 // succeeding intervals until we get past aBEnd.
2909
2910 nscoord lineEdge = aIsLineLeft ? nscoord_MAX : nscoord_MIN;
2911
2912 size_t intervalCount = aIntervals.Length();
2913 for (size_t i = MinIntervalIndexContainingY(aIntervals, aBStart);
2914 i < intervalCount; ++i) {
2915 // We can always get the bCoord from the intervals' mLineLeft,
2916 // since the y() coordinate is duplicated in both points in the
2917 // interval.
2918 auto& interval = aIntervals[i];
2919 nscoord bCoord = interval.Y();
2920 if (bCoord >= aBEnd) {
2921 break;
2922 }
2923 // Get the edge from the interval point indicated by aLeft.
2924 if (aIsLineLeft) {
2925 lineEdge = std::min(lineEdge, interval.X());
2926 } else {
2927 lineEdge = std::max(lineEdge, interval.XMost());
2928 }
2929 }
2930
2931 return lineEdge;
2932 }
2933
2934 /* static */ nsFloatManager::ShapeInfo::dfType
CalcUsedShapeMargin5X(nscoord aShapeMargin,int32_t aAppUnitsPerDevPixel)2935 nsFloatManager::ShapeInfo::CalcUsedShapeMargin5X(nscoord aShapeMargin,
2936 int32_t aAppUnitsPerDevPixel) {
2937 // Our distance field has to be able to hold values equal to the
2938 // maximum shape-margin value that we care about faithfully rendering,
2939 // times 5. A 16-bit unsigned int can represent up to ~ 65K which means
2940 // we can handle a margin up to ~ 13K device pixels. That's good enough
2941 // for practical usage. Any supplied shape-margin value higher than this
2942 // maximum will be clamped.
2943 static const float MAX_MARGIN_5X_FLOAT = (float)MAX_MARGIN_5X;
2944
2945 // Convert aShapeMargin to dev pixels, convert that into 5x-dev-pixel
2946 // space, then clamp to MAX_MARGIN_5X_FLOAT.
2947 float shapeMarginDevPixels5X =
2948 5.0f * NSAppUnitsToFloatPixels(aShapeMargin, aAppUnitsPerDevPixel);
2949 NS_WARNING_ASSERTION(shapeMarginDevPixels5X <= MAX_MARGIN_5X_FLOAT,
2950 "shape-margin is too large and is being clamped.");
2951
2952 // We calculate a minimum in float space, which takes care of any overflow
2953 // or infinity that may have occurred earlier from multiplication of
2954 // too-large aShapeMargin values.
2955 float usedMargin5XFloat =
2956 std::min(shapeMarginDevPixels5X, MAX_MARGIN_5X_FLOAT);
2957 return (dfType)NSToIntRound(usedMargin5XFloat);
2958 }
2959
2960 //----------------------------------------------------------------------
2961
~nsAutoFloatManager()2962 nsAutoFloatManager::~nsAutoFloatManager() {
2963 // Restore the old float manager in the reflow input if necessary.
2964 if (mNew) {
2965 #ifdef DEBUG
2966 if (nsBlockFrame::gNoisyFloatManager) {
2967 printf("restoring old float manager %p\n", mOld);
2968 }
2969 #endif
2970
2971 mReflowInput.mFloatManager = mOld;
2972
2973 #ifdef DEBUG
2974 if (nsBlockFrame::gNoisyFloatManager) {
2975 if (mOld) {
2976 mReflowInput.mFrame->ListTag(stdout);
2977 printf(": float manager %p after reflow\n", mOld);
2978 mOld->List(stdout);
2979 }
2980 }
2981 #endif
2982 }
2983 }
2984
CreateFloatManager(nsPresContext * aPresContext)2985 void nsAutoFloatManager::CreateFloatManager(nsPresContext* aPresContext) {
2986 MOZ_ASSERT(!mNew, "Redundant call to CreateFloatManager!");
2987
2988 // Create a new float manager and install it in the reflow
2989 // input. `Remember' the old float manager so we can restore it
2990 // later.
2991 mNew = MakeUnique<nsFloatManager>(aPresContext->PresShell(),
2992 mReflowInput.GetWritingMode());
2993
2994 #ifdef DEBUG
2995 if (nsBlockFrame::gNoisyFloatManager) {
2996 printf("constructed new float manager %p (replacing %p)\n", mNew.get(),
2997 mReflowInput.mFloatManager);
2998 }
2999 #endif
3000
3001 // Set the float manager in the existing reflow input.
3002 mOld = mReflowInput.mFloatManager;
3003 mReflowInput.mFloatManager = mNew.get();
3004 }
3005