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