blob: 9495d33084162f612cba85cf938d57739d6ca727 [file] [log] [blame]
/*
* Copyright (C) 2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "FloatingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "DisplayBox.h"
#include "FloatAvoider.h"
#include "FloatBox.h"
#include "LayoutBox.h"
#include "LayoutContainer.h"
#include "LayoutState.h"
#include <wtf/IsoMallocInlines.h>
namespace WebCore {
namespace Layout {
WTF_MAKE_ISO_ALLOCATED_IMPL(FloatingContext);
// Finding the top/left position for a new floating(F)
// ____ ____ _____ _______
// | || L2 || | <-----1---->| |
// | ||____|| L3 | | R1 |
// | L1 | |_____| | |
// |____| <-------------2--------->| |
// | |
// |_______|
//
// 1. Compute the initial vertical position for (F) -> (1)
// 2. Find the corresponding floating pair (L3-R1)
// 3. Align (F) horizontally with (L3-R1) depending whether (F) is left/right positioned
// 4. Intersect (F) with (L3-R1)
// 5. If (F) does not fit, find the next floating pair (L1-R1)
// 6. Repeat until either (F) fits/no more floats.
// Note that all coordinates are in the coordinate system of the formatting root.
// The formatting root here is always the one that establishes the floating context (see inherited floating context).
// (It simply means that the float box's formatting root is not necessarily the same as the FormattingContext's root.)
class Iterator;
class FloatPair {
public:
struct LeftRightIndex {
bool isEmpty() const { return !left && !right;}
Optional<unsigned> left;
Optional<unsigned> right;
};
bool isEmpty() const { return m_floatPair.isEmpty(); }
const FloatingState::FloatItem* left() const;
const FloatingState::FloatItem* right() const;
bool intersects(const Display::Rect&) const;
PositionInContextRoot verticalConstraint() const { return m_verticalPosition; }
FloatAvoider::HorizontalConstraints horizontalConstraints() const;
PositionInContextRoot bottom() const;
LeftRightIndex operator*() const { return m_floatPair; };
bool operator==(const FloatPair&) const;
private:
friend class Iterator;
FloatPair(const FloatingState::FloatList&);
const FloatingState::FloatList& m_floats;
LeftRightIndex m_floatPair;
PositionInContextRoot m_verticalPosition;
};
class Iterator {
public:
Iterator(const FloatingState::FloatList&, Optional<PositionInContextRoot> verticalPosition);
const FloatPair& operator*() const { return m_current; }
Iterator& operator++();
bool operator==(const Iterator&) const;
bool operator!=(const Iterator&) const;
private:
void set(PositionInContextRoot verticalPosition);
const FloatingState::FloatList& m_floats;
FloatPair m_current;
};
static Iterator begin(const FloatingState::FloatList& floats, PositionInContextRoot initialVerticalPosition)
{
// Start with the inner-most floating pair for the initial vertical position.
return Iterator(floats, initialVerticalPosition);
}
static Iterator end(const FloatingState::FloatList& floats)
{
return Iterator(floats, { });
}
#ifndef NDEBUG
static bool areFloatsHorizontallySorted(const FloatingState& floatingState)
{
auto& floats = floatingState.floats();
auto rightEdgeOfLeftFloats = LayoutUnit::min();
auto leftEdgeOfRightFloats = LayoutUnit::max();
WTF::Optional<LayoutUnit> leftBottom;
WTF::Optional<LayoutUnit> rightBottom;
for (auto& floatItem : floats) {
if (floatItem.isLeftPositioned()) {
auto rightEdge = floatItem.rectWithMargin().right();
if (rightEdge < rightEdgeOfLeftFloats) {
if (leftBottom && floatItem.rectWithMargin().top() < *leftBottom)
return false;
}
leftBottom = floatItem.rectWithMargin().bottom();
rightEdgeOfLeftFloats = rightEdge;
} else {
auto leftEdge = floatItem.rectWithMargin().left();
if (leftEdge > leftEdgeOfRightFloats) {
if (rightBottom && floatItem.rectWithMargin().top() < *rightBottom)
return false;
}
rightBottom = floatItem.rectWithMargin().bottom();
leftEdgeOfRightFloats = leftEdge;
}
}
return true;
}
#endif
FloatingContext::FloatingContext(FloatingState& floatingState)
: m_floatingState(floatingState)
{
}
Point FloatingContext::positionForFloat(const Box& layoutBox) const
{
ASSERT(layoutBox.isFloatingPositioned());
ASSERT(areFloatsHorizontallySorted(m_floatingState));
if (m_floatingState.isEmpty()) {
auto& displayBox = layoutState().displayBoxForLayoutBox(layoutBox);
auto alignWithContainingBlock = [&]() -> Position {
// If there is no floating to align with, push the box to the left/right edge of its containing block's content box.
auto& containingBlockDisplayBox = layoutState().displayBoxForLayoutBox(*layoutBox.containingBlock());
if (layoutBox.isLeftFloatingPositioned())
return Position { containingBlockDisplayBox.contentBoxLeft() + displayBox.marginStart() };
return Position { containingBlockDisplayBox.contentBoxRight() - displayBox.marginEnd() - displayBox.width() };
};
// No float box on the context yet -> align it with the containing block's left/right edge.
return { alignWithContainingBlock(), displayBox.top() };
}
// Find the top most position where the float box fits.
FloatBox floatBox = { layoutBox, m_floatingState, layoutState() };
findPositionForFloatBox(floatBox);
return floatBox.rectInContainingBlock().topLeft();
}
Optional<Point> FloatingContext::positionForFormattingContextRoot(const Box& layoutBox) const
{
ASSERT(layoutBox.establishesBlockFormattingContext());
ASSERT(!layoutBox.isFloatingPositioned());
ASSERT(!layoutBox.hasFloatClear());
ASSERT(areFloatsHorizontallySorted(m_floatingState));
if (m_floatingState.isEmpty())
return { };
FloatAvoider floatAvoider = { layoutBox, m_floatingState, layoutState() };
findPositionForFormattingContextRoot(floatAvoider);
return { floatAvoider.rectInContainingBlock().topLeft() };
}
FloatingContext::ClearancePosition FloatingContext::verticalPositionWithClearance(const Box& layoutBox) const
{
ASSERT(layoutBox.hasFloatClear());
ASSERT(layoutBox.isBlockLevelBox());
ASSERT(areFloatsHorizontallySorted(m_floatingState));
if (m_floatingState.isEmpty())
return { };
auto bottom = [&](Optional<PositionInContextRoot> floatBottom) -> ClearancePosition {
// 'bottom' is in the formatting root's coordinate system.
if (!floatBottom)
return { };
// 9.5.2 Controlling flow next to floats: the 'clear' property
// Then the amount of clearance is set to the greater of:
//
// 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.
// 2. The amount necessary to place the top border edge of the block at its hypothetical position.
auto& layoutState = this->layoutState();
auto rootRelativeTop = FormattingContext::mapTopToAncestor(layoutState, layoutBox, downcast<Container>(m_floatingState.root()));
auto clearance = *floatBottom - rootRelativeTop;
if (clearance <= 0)
return { };
// Clearance inhibits margin collapsing.
if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
// Does this box with clearance actually collapse its margin before with the previous inflow box's margin after?
auto verticalMargin = layoutState.displayBoxForLayoutBox(layoutBox).verticalMargin();
if (verticalMargin.hasCollapsedValues() && verticalMargin.collapsedValues().before) {
auto previousVerticalMargin = layoutState.displayBoxForLayoutBox(*previousInFlowSibling).verticalMargin();
auto collapsedMargin = *verticalMargin.collapsedValues().before;
auto nonCollapsedMargin = previousVerticalMargin.after() + verticalMargin.before();
auto marginDifference = nonCollapsedMargin - collapsedMargin;
// Move the box to the position where it would be with non-collapsed margins.
rootRelativeTop += marginDifference;
// 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.
clearance -= marginDifference;
}
}
// Now adjust the box's position with the clearance.
rootRelativeTop += clearance;
ASSERT(*floatBottom == rootRelativeTop);
// The return vertical position is in the containing block's coordinate system. Convert it to the formatting root's coordinate system if needed.
if (layoutBox.containingBlock() == &m_floatingState.root())
return { Position { rootRelativeTop }, clearance };
auto containingBlockRootRelativeTop = FormattingContext::mapTopToAncestor(layoutState, *layoutBox.containingBlock(), downcast<Container>(m_floatingState.root()));
return { Position { rootRelativeTop - containingBlockRootRelativeTop }, clearance };
};
auto clear = layoutBox.style().clear();
auto& formattingContextRoot = layoutBox.formattingContextRoot();
if (clear == Clear::Left)
return bottom(m_floatingState.leftBottom(formattingContextRoot));
if (clear == Clear::Right)
return bottom(m_floatingState.rightBottom(formattingContextRoot));
if (clear == Clear::Both)
return bottom(m_floatingState.bottom(formattingContextRoot));
ASSERT_NOT_REACHED();
return { };
}
static FloatPair::LeftRightIndex findAvailablePosition(FloatAvoider& floatAvoider, const FloatingState::FloatList& floats)
{
Optional<PositionInContextRoot> bottomMost;
Optional<FloatPair::LeftRightIndex> innerMostLeftAndRight;
auto end = Layout::end(floats);
for (auto iterator = begin(floats, { floatAvoider.rect().top() }); iterator != end; ++iterator) {
ASSERT(!(*iterator).isEmpty());
auto leftRightFloatPair = *iterator;
innerMostLeftAndRight = innerMostLeftAndRight.valueOr(*leftRightFloatPair);
// Move the box horizontally so that it either
// 1. aligns with the current floating pair
// 2. or with the containing block's content box if there's no float to align with at this vertical position.
floatAvoider.setHorizontalConstraints(leftRightFloatPair.horizontalConstraints());
floatAvoider.setVerticalConstraint(leftRightFloatPair.verticalConstraint());
// Ensure that the float avoider
// 1. does not "overflow" its containing block with the current horiztonal constraints. It simply means that the float avoider's
// containing block could push the candidate position beyond the current float horizontally (too far to the left/right)
// 2. avoids floats on both sides.
if (!floatAvoider.overflowsContainingBlock() && !leftRightFloatPair.intersects(floatAvoider.rect()))
return *innerMostLeftAndRight;
bottomMost = leftRightFloatPair.bottom();
// Move to the next floating pair.
}
// The candidate box is already below of all the floats.
if (!bottomMost)
return { };
// Passed all the floats and still does not fit? Push it below the last float.
floatAvoider.setVerticalConstraint(*bottomMost);
floatAvoider.setHorizontalConstraints({ });
ASSERT(innerMostLeftAndRight);
return *innerMostLeftAndRight;
}
void FloatingContext::findPositionForFloatBox(FloatBox& floatBox) const
{
findAvailablePosition(floatBox, m_floatingState.floats());
}
void FloatingContext::findPositionForFormattingContextRoot(FloatAvoider& floatAvoider) const
{
// A non-floating formatting root's initial vertical position is its static position.
// It means that such boxes can end up vertically placed in-between existing floats (which is
// never the case for floats, since they cannot be placed above existing floats).
// ____ ____
// | || F1 |
// | L1 | ----
// | | ________
// ---- | R1 |
// --------
// Document order: 1. float: left (L1) 2. float: right (R1) 3. formatting root (F1)
//
// 1. Probe for available placement at initial position (note it runs a backward probing algorithm at a specific vertical position)
// 2. Check if there's any intersecing float below (forward seaching)
// 3. Align the box with the intersected float and probe for placement again (#1).
auto& floats = m_floatingState.floats();
while (true) {
auto innerMostLeftAndRight = findAvailablePosition(floatAvoider, floats);
if (innerMostLeftAndRight.isEmpty())
return;
auto overlappingFloatBox = [&floats](auto startFloatIndex, auto floatAvoiderRect) -> const FloatingState::FloatItem* {
for (auto i = startFloatIndex; i < floats.size(); ++i) {
auto& floatBox = floats[i];
if (floatBox.rectWithMargin().intersects(floatAvoiderRect))
return &floatBox;
}
return nullptr;
};
auto startIndex = std::max(innerMostLeftAndRight.left.valueOr(0), innerMostLeftAndRight.right.valueOr(0)) + 1;
auto* intersectedFloatBox = overlappingFloatBox(startIndex, floatAvoider.rect());
if (!intersectedFloatBox)
return;
floatAvoider.setVerticalConstraint({ intersectedFloatBox->rectWithMargin().top() });
}
}
FloatPair::FloatPair(const FloatingState::FloatList& floats)
: m_floats(floats)
{
}
const FloatingState::FloatItem* FloatPair::left() const
{
if (!m_floatPair.left)
return nullptr;
ASSERT(m_floats[*m_floatPair.left].isLeftPositioned());
return &m_floats[*m_floatPair.left];
}
const FloatingState::FloatItem* FloatPair::right() const
{
if (!m_floatPair.right)
return nullptr;
ASSERT(!m_floats[*m_floatPair.right].isLeftPositioned());
return &m_floats[*m_floatPair.right];
}
bool FloatPair::intersects(const Display::Rect& floatAvoiderRect) const
{
auto intersects = [&](auto* floating) {
return floating && floating->rectWithMargin().intersects(floatAvoiderRect);
};
ASSERT(!m_floatPair.isEmpty());
return intersects(left()) || intersects(right());
}
bool FloatPair::operator ==(const FloatPair& other) const
{
return m_floatPair.left == other.m_floatPair.left && m_floatPair.right == other.m_floatPair.right;
}
FloatAvoider::HorizontalConstraints FloatPair::horizontalConstraints() const
{
Optional<PositionInContextRoot> leftEdge;
Optional<PositionInContextRoot> rightEdge;
if (left())
leftEdge = PositionInContextRoot { left()->rectWithMargin().right() };
if (right())
rightEdge = PositionInContextRoot { right()->rectWithMargin().left() };
return { leftEdge, rightEdge };
}
PositionInContextRoot FloatPair::bottom() const
{
auto* left = this->left();
auto* right = this->right();
ASSERT(left || right);
auto leftBottom = left ? Optional<PositionInContextRoot>(PositionInContextRoot { left->rectWithMargin().bottom() }) : WTF::nullopt;
auto rightBottom = right ? Optional<PositionInContextRoot>(PositionInContextRoot { right->rectWithMargin().bottom() }) : WTF::nullopt;
if (leftBottom && rightBottom)
return std::max(*leftBottom, *rightBottom);
if (leftBottom)
return *leftBottom;
return *rightBottom;
}
Iterator::Iterator(const FloatingState::FloatList& floats, Optional<PositionInContextRoot> verticalPosition)
: m_floats(floats)
, m_current(floats)
{
if (verticalPosition)
set(*verticalPosition);
}
inline static Optional<unsigned> previousFloatingIndex(Float floatingType, const FloatingState::FloatList& floats, unsigned currentIndex)
{
RELEASE_ASSERT(currentIndex <= floats.size());
while (currentIndex) {
auto& floating = floats[--currentIndex];
if ((floatingType == Float::Left && floating.isLeftPositioned()) || (floatingType == Float::Right && !floating.isLeftPositioned()))
return currentIndex;
}
return { };
}
Iterator& Iterator::operator++()
{
if (m_current.isEmpty()) {
ASSERT_NOT_REACHED();
return *this;
}
auto findPreviousFloatingWithLowerBottom = [&](Float floatingType, unsigned currentIndex) -> Optional<unsigned> {
RELEASE_ASSERT(currentIndex < m_floats.size());
// Last floating? There's certainly no previous floating at this point.
if (!currentIndex)
return { };
auto currentBottom = m_floats[currentIndex].rectWithMargin().bottom();
Optional<unsigned> index = currentIndex;
while (true) {
index = previousFloatingIndex(floatingType, m_floats, *index);
if (!index)
return { };
if (m_floats[*index].rectWithMargin().bottom() > currentBottom)
return index;
}
ASSERT_NOT_REACHED();
return { };
};
// 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).
// The current floats from left and right are considered the inner-most pair for the current vertical position.
// 2. Move away from inner-most pair by picking one of the previous floats in the list(#1)
// 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).
// 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.
// As the result we have more horizontal space on the current vertical position.
auto leftBottom = m_current.left() ? Optional<PositionInContextRoot>(m_current.left()->bottom()) : WTF::nullopt;
auto rightBottom = m_current.right() ? Optional<PositionInContextRoot>(m_current.right()->bottom()) : WTF::nullopt;
auto updateLeft = (leftBottom == rightBottom) || (!rightBottom || (leftBottom && leftBottom < rightBottom));
auto updateRight = (leftBottom == rightBottom) || (!leftBottom || (rightBottom && leftBottom > rightBottom));
if (updateLeft) {
ASSERT(m_current.m_floatPair.left);
m_current.m_verticalPosition = *leftBottom;
m_current.m_floatPair.left = findPreviousFloatingWithLowerBottom(Float::Left, *m_current.m_floatPair.left);
}
if (updateRight) {
ASSERT(m_current.m_floatPair.right);
m_current.m_verticalPosition = *rightBottom;
m_current.m_floatPair.right = findPreviousFloatingWithLowerBottom(Float::Right, *m_current.m_floatPair.right);
}
return *this;
}
void Iterator::set(PositionInContextRoot verticalPosition)
{
// Move the iterator to the initial vertical position by starting at the inner-most floating pair (last floats on left/right).
// 1. Check if the inner-most pair covers the vertical position.
// 2. Move outwards from the inner-most pair until the vertical postion intersects.
m_current.m_verticalPosition = verticalPosition;
// No floats at all?
if (m_floats.isEmpty()) {
ASSERT_NOT_REACHED();
m_current.m_floatPair = { };
return;
}
auto findFloatingBelow = [&](Float floatingType) -> Optional<unsigned> {
ASSERT(!m_floats.isEmpty());
auto index = floatingType == Float::Left ? m_current.m_floatPair.left : m_current.m_floatPair.right;
// Start from the end if we don't have current yet.
index = index.valueOr(m_floats.size());
while (true) {
index = previousFloatingIndex(floatingType, m_floats, *index);
if (!index)
return { };
// Is this floating intrusive on this position?
auto rect = m_floats[*index].rectWithMargin();
if (rect.top() <= verticalPosition && rect.bottom() > verticalPosition)
return index;
}
return { };
};
m_current.m_floatPair.left = findFloatingBelow(Float::Left);
m_current.m_floatPair.right = findFloatingBelow(Float::Right);
ASSERT(!m_current.m_floatPair.left || (*m_current.m_floatPair.left < m_floats.size() && m_floats[*m_current.m_floatPair.left].isLeftPositioned()));
ASSERT(!m_current.m_floatPair.right || (*m_current.m_floatPair.right < m_floats.size() && !m_floats[*m_current.m_floatPair.right].isLeftPositioned()));
}
bool Iterator::operator==(const Iterator& other) const
{
return m_current == other.m_current;
}
bool Iterator::operator!=(const Iterator& other) const
{
return !(*this == other);
}
}
}
#endif