Coverage Report

Created: 2018-09-25 14:53

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