| 1 | /* |
| 2 | * Copyright (C) 2018 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| 14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| 15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| 17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| 23 | * THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "FloatingContext.h" |
| 28 | |
| 29 | #if ENABLE(LAYOUT_FORMATTING_CONTEXT) |
| 30 | |
| 31 | #include "DisplayBox.h" |
| 32 | #include "FloatAvoider.h" |
| 33 | #include "FloatBox.h" |
| 34 | #include "LayoutBox.h" |
| 35 | #include "LayoutContainer.h" |
| 36 | #include "LayoutState.h" |
| 37 | #include <wtf/IsoMallocInlines.h> |
| 38 | |
| 39 | namespace WebCore { |
| 40 | namespace Layout { |
| 41 | |
| 42 | WTF_MAKE_ISO_ALLOCATED_IMPL(FloatingContext); |
| 43 | |
| 44 | // Finding the top/left position for a new floating(F) |
| 45 | // ____ ____ _____ _______ |
| 46 | // | || L2 || | <-----1---->| | |
| 47 | // | ||____|| L3 | | R1 | |
| 48 | // | L1 | |_____| | | |
| 49 | // |____| <-------------2--------->| | |
| 50 | // | | |
| 51 | // |_______| |
| 52 | // |
| 53 | // 1. Compute the initial vertical position for (F) -> (1) |
| 54 | // 2. Find the corresponding floating pair (L3-R1) |
| 55 | // 3. Align (F) horizontally with (L3-R1) depending whether (F) is left/right positioned |
| 56 | // 4. Intersect (F) with (L3-R1) |
| 57 | // 5. If (F) does not fit, find the next floating pair (L1-R1) |
| 58 | // 6. Repeat until either (F) fits/no more floats. |
| 59 | // Note that all coordinates are in the coordinate system of the formatting root. |
| 60 | // The formatting root here is always the one that establishes the floating context (see inherited floating context). |
| 61 | // (It simply means that the float box's formatting root is not necessarily the same as the FormattingContext's root.) |
| 62 | |
| 63 | class Iterator; |
| 64 | |
| 65 | class FloatPair { |
| 66 | public: |
| 67 | struct LeftRightIndex { |
| 68 | bool isEmpty() const { return !left && !right;} |
| 69 | |
| 70 | Optional<unsigned> left; |
| 71 | Optional<unsigned> right; |
| 72 | }; |
| 73 | |
| 74 | bool isEmpty() const { return m_floatPair.isEmpty(); } |
| 75 | const FloatingState::FloatItem* left() const; |
| 76 | const FloatingState::FloatItem* right() const; |
| 77 | bool intersects(const Display::Box::Rect&) const; |
| 78 | PositionInContextRoot verticalConstraint() const { return m_verticalPosition; } |
| 79 | FloatAvoider::HorizontalConstraints horizontalConstraints() const; |
| 80 | PositionInContextRoot bottom() const; |
| 81 | LeftRightIndex operator*() const { return m_floatPair; }; |
| 82 | bool operator==(const FloatPair&) const; |
| 83 | |
| 84 | private: |
| 85 | friend class Iterator; |
| 86 | FloatPair(const FloatingState::FloatList&); |
| 87 | |
| 88 | const FloatingState::FloatList& m_floats; |
| 89 | LeftRightIndex m_floatPair; |
| 90 | PositionInContextRoot m_verticalPosition; |
| 91 | }; |
| 92 | |
| 93 | class Iterator { |
| 94 | public: |
| 95 | Iterator(const FloatingState::FloatList&, Optional<PositionInContextRoot> verticalPosition); |
| 96 | |
| 97 | const FloatPair& operator*() const { return m_current; } |
| 98 | Iterator& operator++(); |
| 99 | bool operator==(const Iterator&) const; |
| 100 | bool operator!=(const Iterator&) const; |
| 101 | |
| 102 | private: |
| 103 | void set(PositionInContextRoot verticalPosition); |
| 104 | |
| 105 | const FloatingState::FloatList& m_floats; |
| 106 | FloatPair m_current; |
| 107 | }; |
| 108 | |
| 109 | static Iterator begin(const FloatingState::FloatList& floats, PositionInContextRoot initialVerticalPosition) |
| 110 | { |
| 111 | // Start with the inner-most floating pair for the initial vertical position. |
| 112 | return Iterator(floats, initialVerticalPosition); |
| 113 | } |
| 114 | |
| 115 | static Iterator end(const FloatingState::FloatList& floats) |
| 116 | { |
| 117 | return Iterator(floats, { }); |
| 118 | } |
| 119 | |
| 120 | #ifndef NDEBUG |
| 121 | static bool areFloatsHorizontallySorted(const FloatingState& floatingState) |
| 122 | { |
| 123 | auto& floats = floatingState.floats(); |
| 124 | auto rightEdgeOfLeftFloats = LayoutUnit::min(); |
| 125 | auto leftEdgeOfRightFloats = LayoutUnit::max(); |
| 126 | WTF::Optional<LayoutUnit> leftBottom; |
| 127 | WTF::Optional<LayoutUnit> rightBottom; |
| 128 | |
| 129 | for (auto& floatItem : floats) { |
| 130 | if (floatItem.isLeftPositioned()) { |
| 131 | auto rightEdge = floatItem.rectWithMargin().right(); |
| 132 | if (rightEdge < rightEdgeOfLeftFloats) { |
| 133 | if (leftBottom && floatItem.rectWithMargin().top() < *leftBottom) |
| 134 | return false; |
| 135 | } |
| 136 | leftBottom = floatItem.rectWithMargin().bottom(); |
| 137 | rightEdgeOfLeftFloats = rightEdge; |
| 138 | } else { |
| 139 | auto leftEdge = floatItem.rectWithMargin().left(); |
| 140 | if (leftEdge > leftEdgeOfRightFloats) { |
| 141 | if (rightBottom && floatItem.rectWithMargin().top() < *rightBottom) |
| 142 | return false; |
| 143 | } |
| 144 | rightBottom = floatItem.rectWithMargin().bottom(); |
| 145 | leftEdgeOfRightFloats = leftEdge; |
| 146 | } |
| 147 | } |
| 148 | return true; |
| 149 | } |
| 150 | #endif |
| 151 | |
| 152 | FloatingContext::FloatingContext(FloatingState& floatingState) |
| 153 | : m_floatingState(floatingState) |
| 154 | { |
| 155 | } |
| 156 | |
| 157 | Point FloatingContext::positionForFloat(const Box& layoutBox) const |
| 158 | { |
| 159 | ASSERT(layoutBox.isFloatingPositioned()); |
| 160 | ASSERT(areFloatsHorizontallySorted(m_floatingState)); |
| 161 | |
| 162 | if (m_floatingState.isEmpty()) { |
| 163 | auto& displayBox = layoutState().displayBoxForLayoutBox(layoutBox); |
| 164 | |
| 165 | auto alignWithContainingBlock = [&]() -> Position { |
| 166 | // If there is no floating to align with, push the box to the left/right edge of its containing block's content box. |
| 167 | auto& containingBlockDisplayBox = layoutState().displayBoxForLayoutBox(*layoutBox.containingBlock()); |
| 168 | |
| 169 | if (layoutBox.isLeftFloatingPositioned()) |
| 170 | return Position { containingBlockDisplayBox.contentBoxLeft() + displayBox.marginStart() }; |
| 171 | |
| 172 | return Position { containingBlockDisplayBox.contentBoxRight() - displayBox.marginEnd() - displayBox.width() }; |
| 173 | }; |
| 174 | |
| 175 | // No float box on the context yet -> align it with the containing block's left/right edge. |
| 176 | return { alignWithContainingBlock(), displayBox.top() }; |
| 177 | } |
| 178 | |
| 179 | // Find the top most position where the float box fits. |
| 180 | FloatBox floatBox = { layoutBox, m_floatingState, layoutState() }; |
| 181 | findPositionForFloatBox(floatBox); |
| 182 | return floatBox.rectInContainingBlock().topLeft(); |
| 183 | } |
| 184 | |
| 185 | Optional<Point> FloatingContext::positionForFormattingContextRoot(const Box& layoutBox) const |
| 186 | { |
| 187 | ASSERT(layoutBox.establishesBlockFormattingContext()); |
| 188 | ASSERT(!layoutBox.isFloatingPositioned()); |
| 189 | ASSERT(!layoutBox.hasFloatClear()); |
| 190 | ASSERT(areFloatsHorizontallySorted(m_floatingState)); |
| 191 | |
| 192 | if (m_floatingState.isEmpty()) |
| 193 | return { }; |
| 194 | |
| 195 | FloatAvoider floatAvoider = { layoutBox, m_floatingState, layoutState() }; |
| 196 | findPositionForFormattingContextRoot(floatAvoider); |
| 197 | return { floatAvoider.rectInContainingBlock().topLeft() }; |
| 198 | } |
| 199 | |
| 200 | FloatingContext::ClearancePosition FloatingContext::verticalPositionWithClearance(const Box& layoutBox) const |
| 201 | { |
| 202 | ASSERT(layoutBox.hasFloatClear()); |
| 203 | ASSERT(layoutBox.isBlockLevelBox()); |
| 204 | ASSERT(areFloatsHorizontallySorted(m_floatingState)); |
| 205 | |
| 206 | if (m_floatingState.isEmpty()) |
| 207 | return { }; |
| 208 | |
| 209 | auto bottom = [&](Optional<PositionInContextRoot> floatBottom) -> ClearancePosition { |
| 210 | // 'bottom' is in the formatting root's coordinate system. |
| 211 | if (!floatBottom) |
| 212 | return { }; |
| 213 | |
| 214 | // 9.5.2 Controlling flow next to floats: the 'clear' property |
| 215 | // Then the amount of clearance is set to the greater of: |
| 216 | // |
| 217 | // 1. The amount necessary to place the border edge of the block even with the bottom outer edge of the lowest float that is to be cleared. |
| 218 | // 2. The amount necessary to place the top border edge of the block at its hypothetical position. |
| 219 | auto& layoutState = this->layoutState(); |
| 220 | auto rootRelativeTop = FormattingContext::mapTopToAncestor(layoutState, layoutBox, downcast<Container>(m_floatingState.root())); |
| 221 | auto clearance = *floatBottom - rootRelativeTop; |
| 222 | if (clearance <= 0) |
| 223 | return { }; |
| 224 | |
| 225 | // Clearance inhibits margin collapsing. |
| 226 | if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) { |
| 227 | // Does this box with clearance actually collapse its margin before with the previous inflow box's margin after? |
| 228 | auto verticalMargin = layoutState.displayBoxForLayoutBox(layoutBox).verticalMargin(); |
| 229 | if (verticalMargin.hasCollapsedValues() && verticalMargin.collapsedValues().before) { |
| 230 | auto previousVerticalMargin = layoutState.displayBoxForLayoutBox(*previousInFlowSibling).verticalMargin(); |
| 231 | auto collapsedMargin = *verticalMargin.collapsedValues().before; |
| 232 | auto nonCollapsedMargin = previousVerticalMargin.after() + verticalMargin.before(); |
| 233 | auto marginDifference = nonCollapsedMargin - collapsedMargin; |
| 234 | // Move the box to the position where it would be with non-collapsed margins. |
| 235 | rootRelativeTop += marginDifference; |
| 236 | // Having negative clearance is also normal. It just means that the box with the non-collapsed margins is now lower than it needs to be. |
| 237 | clearance -= marginDifference; |
| 238 | } |
| 239 | } |
| 240 | // Now adjust the box's position with the clearance. |
| 241 | rootRelativeTop += clearance; |
| 242 | ASSERT(*floatBottom == rootRelativeTop); |
| 243 | |
| 244 | // The return vertical position is in the containing block's coordinate system. Convert it to the formatting root's coordinate system if needed. |
| 245 | if (layoutBox.containingBlock() == &m_floatingState.root()) |
| 246 | return { Position { rootRelativeTop }, clearance }; |
| 247 | |
| 248 | auto containingBlockRootRelativeTop = FormattingContext::mapTopToAncestor(layoutState, *layoutBox.containingBlock(), downcast<Container>(m_floatingState.root())); |
| 249 | return { Position { rootRelativeTop - containingBlockRootRelativeTop }, clearance }; |
| 250 | }; |
| 251 | |
| 252 | auto clear = layoutBox.style().clear(); |
| 253 | auto& formattingContextRoot = layoutBox.formattingContextRoot(); |
| 254 | |
| 255 | if (clear == Clear::Left) |
| 256 | return bottom(m_floatingState.leftBottom(formattingContextRoot)); |
| 257 | |
| 258 | if (clear == Clear::Right) |
| 259 | return bottom(m_floatingState.rightBottom(formattingContextRoot)); |
| 260 | |
| 261 | if (clear == Clear::Both) |
| 262 | return bottom(m_floatingState.bottom(formattingContextRoot)); |
| 263 | |
| 264 | ASSERT_NOT_REACHED(); |
| 265 | return { }; |
| 266 | } |
| 267 | |
| 268 | static FloatPair::LeftRightIndex findAvailablePosition(FloatAvoider& floatAvoider, const FloatingState::FloatList& floats) |
| 269 | { |
| 270 | Optional<PositionInContextRoot> bottomMost; |
| 271 | Optional<FloatPair::LeftRightIndex> innerMostLeftAndRight; |
| 272 | auto end = Layout::end(floats); |
| 273 | for (auto iterator = begin(floats, { floatAvoider.rect().top() }); iterator != end; ++iterator) { |
| 274 | ASSERT(!(*iterator).isEmpty()); |
| 275 | auto leftRightFloatPair = *iterator; |
| 276 | innerMostLeftAndRight = innerMostLeftAndRight.valueOr(*leftRightFloatPair); |
| 277 | |
| 278 | // Move the box horizontally so that it either |
| 279 | // 1. aligns with the current floating pair |
| 280 | // 2. or with the containing block's content box if there's no float to align with at this vertical position. |
| 281 | floatAvoider.setHorizontalConstraints(leftRightFloatPair.horizontalConstraints()); |
| 282 | floatAvoider.setVerticalConstraint(leftRightFloatPair.verticalConstraint()); |
| 283 | |
| 284 | // Ensure that the float avoider |
| 285 | // 1. does not "overflow" its containing block with the current horiztonal constraints. It simply means that the float avoider's |
| 286 | // containing block could push the candidate position beyond the current float horizontally (too far to the left/right) |
| 287 | // 2. avoids floats on both sides. |
| 288 | if (!floatAvoider.overflowsContainingBlock() && !leftRightFloatPair.intersects(floatAvoider.rect())) |
| 289 | return *innerMostLeftAndRight; |
| 290 | |
| 291 | bottomMost = leftRightFloatPair.bottom(); |
| 292 | // Move to the next floating pair. |
| 293 | } |
| 294 | |
| 295 | // The candidate box is already below of all the floats. |
| 296 | if (!bottomMost) |
| 297 | return { }; |
| 298 | |
| 299 | // Passed all the floats and still does not fit? Push it below the last float. |
| 300 | floatAvoider.setVerticalConstraint(*bottomMost); |
| 301 | floatAvoider.setHorizontalConstraints({ }); |
| 302 | ASSERT(innerMostLeftAndRight); |
| 303 | return *innerMostLeftAndRight; |
| 304 | } |
| 305 | |
| 306 | void FloatingContext::findPositionForFloatBox(FloatBox& floatBox) const |
| 307 | { |
| 308 | findAvailablePosition(floatBox, m_floatingState.floats()); |
| 309 | } |
| 310 | |
| 311 | void FloatingContext::findPositionForFormattingContextRoot(FloatAvoider& floatAvoider) const |
| 312 | { |
| 313 | // A non-floating formatting root's initial vertical position is its static position. |
| 314 | // It means that such boxes can end up vertically placed in-between existing floats (which is |
| 315 | // never the case for floats, since they cannot be placed above existing floats). |
| 316 | // ____ ____ |
| 317 | // | || F1 | |
| 318 | // | L1 | ---- |
| 319 | // | | ________ |
| 320 | // ---- | R1 | |
| 321 | // -------- |
| 322 | // Document order: 1. float: left (L1) 2. float: right (R1) 3. formatting root (F1) |
| 323 | // |
| 324 | // 1. Probe for available placement at initial position (note it runs a backward probing algorithm at a specific vertical position) |
| 325 | // 2. Check if there's any intersecing float below (forward seaching) |
| 326 | // 3. Align the box with the intersected float and probe for placement again (#1). |
| 327 | auto& floats = m_floatingState.floats(); |
| 328 | while (true) { |
| 329 | auto innerMostLeftAndRight = findAvailablePosition(floatAvoider, floats); |
| 330 | if (innerMostLeftAndRight.isEmpty()) |
| 331 | return; |
| 332 | |
| 333 | auto overlappingFloatBox = [&floats](auto startFloatIndex, auto floatAvoiderRect) -> const FloatingState::FloatItem* { |
| 334 | for (auto i = startFloatIndex; i < floats.size(); ++i) { |
| 335 | auto& floatBox = floats[i]; |
| 336 | if (floatBox.rectWithMargin().intersects(floatAvoiderRect)) |
| 337 | return &floatBox; |
| 338 | } |
| 339 | return nullptr; |
| 340 | }; |
| 341 | |
| 342 | auto startIndex = std::max(innerMostLeftAndRight.left.valueOr(0), innerMostLeftAndRight.right.valueOr(0)) + 1; |
| 343 | auto* intersectedFloatBox = overlappingFloatBox(startIndex, floatAvoider.rect()); |
| 344 | if (!intersectedFloatBox) |
| 345 | return; |
| 346 | floatAvoider.setVerticalConstraint({ intersectedFloatBox->rectWithMargin().top() }); |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | FloatPair::FloatPair(const FloatingState::FloatList& floats) |
| 351 | : m_floats(floats) |
| 352 | { |
| 353 | } |
| 354 | |
| 355 | const FloatingState::FloatItem* FloatPair::left() const |
| 356 | { |
| 357 | if (!m_floatPair.left) |
| 358 | return nullptr; |
| 359 | |
| 360 | ASSERT(m_floats[*m_floatPair.left].isLeftPositioned()); |
| 361 | return &m_floats[*m_floatPair.left]; |
| 362 | } |
| 363 | |
| 364 | const FloatingState::FloatItem* FloatPair::right() const |
| 365 | { |
| 366 | if (!m_floatPair.right) |
| 367 | return nullptr; |
| 368 | |
| 369 | ASSERT(!m_floats[*m_floatPair.right].isLeftPositioned()); |
| 370 | return &m_floats[*m_floatPair.right]; |
| 371 | } |
| 372 | |
| 373 | bool FloatPair::intersects(const Display::Box::Rect& floatAvoiderRect) const |
| 374 | { |
| 375 | auto intersects = [&](auto* floating) { |
| 376 | return floating && floating->rectWithMargin().intersects(floatAvoiderRect); |
| 377 | }; |
| 378 | |
| 379 | ASSERT(!m_floatPair.isEmpty()); |
| 380 | return intersects(left()) || intersects(right()); |
| 381 | } |
| 382 | |
| 383 | bool FloatPair::operator ==(const FloatPair& other) const |
| 384 | { |
| 385 | return m_floatPair.left == other.m_floatPair.left && m_floatPair.right == other.m_floatPair.right; |
| 386 | } |
| 387 | |
| 388 | FloatAvoider::HorizontalConstraints FloatPair::horizontalConstraints() const |
| 389 | { |
| 390 | Optional<PositionInContextRoot> leftEdge; |
| 391 | Optional<PositionInContextRoot> rightEdge; |
| 392 | |
| 393 | if (left()) |
| 394 | leftEdge = PositionInContextRoot { left()->rectWithMargin().right() }; |
| 395 | |
| 396 | if (right()) |
| 397 | rightEdge = PositionInContextRoot { right()->rectWithMargin().left() }; |
| 398 | |
| 399 | return { leftEdge, rightEdge }; |
| 400 | } |
| 401 | |
| 402 | PositionInContextRoot FloatPair::bottom() const |
| 403 | { |
| 404 | auto* left = this->left(); |
| 405 | auto* right = this->right(); |
| 406 | ASSERT(left || right); |
| 407 | |
| 408 | auto leftBottom = left ? Optional<PositionInContextRoot>(PositionInContextRoot { left->rectWithMargin().bottom() }) : WTF::nullopt; |
| 409 | auto rightBottom = right ? Optional<PositionInContextRoot>(PositionInContextRoot { right->rectWithMargin().bottom() }) : WTF::nullopt; |
| 410 | |
| 411 | if (leftBottom && rightBottom) |
| 412 | return std::max(*leftBottom, *rightBottom); |
| 413 | |
| 414 | if (leftBottom) |
| 415 | return *leftBottom; |
| 416 | |
| 417 | return *rightBottom; |
| 418 | } |
| 419 | |
| 420 | Iterator::Iterator(const FloatingState::FloatList& floats, Optional<PositionInContextRoot> verticalPosition) |
| 421 | : m_floats(floats) |
| 422 | , m_current(floats) |
| 423 | { |
| 424 | if (verticalPosition) |
| 425 | set(*verticalPosition); |
| 426 | } |
| 427 | |
| 428 | inline static Optional<unsigned> previousFloatingIndex(Float floatingType, const FloatingState::FloatList& floats, unsigned currentIndex) |
| 429 | { |
| 430 | RELEASE_ASSERT(currentIndex <= floats.size()); |
| 431 | |
| 432 | while (currentIndex) { |
| 433 | auto& floating = floats[--currentIndex]; |
| 434 | if ((floatingType == Float::Left && floating.isLeftPositioned()) || (floatingType == Float::Right && !floating.isLeftPositioned())) |
| 435 | return currentIndex; |
| 436 | } |
| 437 | |
| 438 | return { }; |
| 439 | } |
| 440 | |
| 441 | Iterator& Iterator::operator++() |
| 442 | { |
| 443 | if (m_current.isEmpty()) { |
| 444 | ASSERT_NOT_REACHED(); |
| 445 | return *this; |
| 446 | } |
| 447 | |
| 448 | auto findPreviousFloatingWithLowerBottom = [&](Float floatingType, unsigned currentIndex) -> Optional<unsigned> { |
| 449 | |
| 450 | RELEASE_ASSERT(currentIndex < m_floats.size()); |
| 451 | |
| 452 | // Last floating? There's certainly no previous floating at this point. |
| 453 | if (!currentIndex) |
| 454 | return { }; |
| 455 | |
| 456 | auto currentBottom = m_floats[currentIndex].rectWithMargin().bottom(); |
| 457 | |
| 458 | Optional<unsigned> index = currentIndex; |
| 459 | while (true) { |
| 460 | index = previousFloatingIndex(floatingType, m_floats, *index); |
| 461 | if (!index) |
| 462 | return { }; |
| 463 | |
| 464 | if (m_floats[*index].rectWithMargin().bottom() > currentBottom) |
| 465 | return index; |
| 466 | } |
| 467 | |
| 468 | ASSERT_NOT_REACHED(); |
| 469 | return { }; |
| 470 | }; |
| 471 | |
| 472 | // 1. Take the current floating from left and right and check which one's bottom edge is positioned higher (they could be on the same vertical position too). |
| 473 | // The current floats from left and right are considered the inner-most pair for the current vertical position. |
| 474 | // 2. Move away from inner-most pair by picking one of the previous floats in the list(#1) |
| 475 | // Ensure that the new floating's bottom edge is positioned lower than the current one -which essentially means skipping in-between floats that are positioned higher). |
| 476 | // 3. Reset the vertical position and align it with the new left-right pair. These floats are now the inner-most boxes for the current vertical position. |
| 477 | // As the result we have more horizontal space on the current vertical position. |
| 478 | auto leftBottom = m_current.left() ? Optional<PositionInContextRoot>(m_current.left()->bottom()) : WTF::nullopt; |
| 479 | auto rightBottom = m_current.right() ? Optional<PositionInContextRoot>(m_current.right()->bottom()) : WTF::nullopt; |
| 480 | |
| 481 | auto updateLeft = (leftBottom == rightBottom) || (!rightBottom || (leftBottom && leftBottom < rightBottom)); |
| 482 | auto updateRight = (leftBottom == rightBottom) || (!leftBottom || (rightBottom && leftBottom > rightBottom)); |
| 483 | |
| 484 | if (updateLeft) { |
| 485 | ASSERT(m_current.m_floatPair.left); |
| 486 | m_current.m_verticalPosition = *leftBottom; |
| 487 | m_current.m_floatPair.left = findPreviousFloatingWithLowerBottom(Float::Left, *m_current.m_floatPair.left); |
| 488 | } |
| 489 | |
| 490 | if (updateRight) { |
| 491 | ASSERT(m_current.m_floatPair.right); |
| 492 | m_current.m_verticalPosition = *rightBottom; |
| 493 | m_current.m_floatPair.right = findPreviousFloatingWithLowerBottom(Float::Right, *m_current.m_floatPair.right); |
| 494 | } |
| 495 | |
| 496 | return *this; |
| 497 | } |
| 498 | |
| 499 | void Iterator::set(PositionInContextRoot verticalPosition) |
| 500 | { |
| 501 | // Move the iterator to the initial vertical position by starting at the inner-most floating pair (last floats on left/right). |
| 502 | // 1. Check if the inner-most pair covers the vertical position. |
| 503 | // 2. Move outwards from the inner-most pair until the vertical postion intersects. |
| 504 | m_current.m_verticalPosition = verticalPosition; |
| 505 | // No floats at all? |
| 506 | if (m_floats.isEmpty()) { |
| 507 | ASSERT_NOT_REACHED(); |
| 508 | m_current.m_floatPair = { }; |
| 509 | return; |
| 510 | } |
| 511 | |
| 512 | auto findFloatingBelow = [&](Float floatingType) -> Optional<unsigned> { |
| 513 | |
| 514 | ASSERT(!m_floats.isEmpty()); |
| 515 | |
| 516 | auto index = floatingType == Float::Left ? m_current.m_floatPair.left : m_current.m_floatPair.right; |
| 517 | // Start from the end if we don't have current yet. |
| 518 | index = index.valueOr(m_floats.size()); |
| 519 | while (true) { |
| 520 | index = previousFloatingIndex(floatingType, m_floats, *index); |
| 521 | if (!index) |
| 522 | return { }; |
| 523 | |
| 524 | // Is this floating intrusive on this position? |
| 525 | auto rect = m_floats[*index].rectWithMargin(); |
| 526 | if (rect.top() <= verticalPosition && rect.bottom() > verticalPosition) |
| 527 | return index; |
| 528 | } |
| 529 | |
| 530 | return { }; |
| 531 | }; |
| 532 | |
| 533 | m_current.m_floatPair.left = findFloatingBelow(Float::Left); |
| 534 | m_current.m_floatPair.right = findFloatingBelow(Float::Right); |
| 535 | |
| 536 | ASSERT(!m_current.m_floatPair.left || (*m_current.m_floatPair.left < m_floats.size() && m_floats[*m_current.m_floatPair.left].isLeftPositioned())); |
| 537 | ASSERT(!m_current.m_floatPair.right || (*m_current.m_floatPair.right < m_floats.size() && !m_floats[*m_current.m_floatPair.right].isLeftPositioned())); |
| 538 | } |
| 539 | |
| 540 | bool Iterator::operator==(const Iterator& other) const |
| 541 | { |
| 542 | return m_current == other.m_current; |
| 543 | } |
| 544 | |
| 545 | bool Iterator::operator!=(const Iterator& other) const |
| 546 | { |
| 547 | return !(*this == other); |
| 548 | } |
| 549 | |
| 550 | } |
| 551 | } |
| 552 | #endif |
| 553 | |