/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 | } |