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