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