BlockFormattingContextGeometry.cpp [plain text]
#include "config.h"
#include "BlockFormattingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "FormattingContext.h"
#include "LayoutChildIterator.h"
#include "Logging.h"
#include <wtf/text/TextStream.h>
namespace WebCore {
namespace Layout {
static bool isStretchedToViewport(const LayoutContext& layoutContext, const Box& layoutBox)
{
ASSERT(layoutBox.isInFlow());
if (!layoutContext.inQuirksMode())
return false;
if (!layoutBox.isDocumentBox() && !layoutBox.isBodyBox())
return false;
return layoutBox.style().logicalHeight().isAuto();
}
static const Container& initialContainingBlock(const Box& layoutBox)
{
auto* containingBlock = layoutBox.containingBlock();
while (containingBlock->containingBlock())
containingBlock = containingBlock->containingBlock();
return *containingBlock;
}
FormattingContext::Geometry::HeightAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedHeightAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
{
ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
auto compute = [&]() -> FormattingContext::Geometry::HeightAndMargin {
auto& style = layoutBox.style();
auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->contentBoxWidth();
auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
Display::Box::VerticalEdges nonCollapsedMargin = { FormattingContext::Geometry::computedValueIfNotAuto(style.marginTop(), containingBlockWidth).value_or(0),
FormattingContext::Geometry::computedValueIfNotAuto(style.marginBottom(), containingBlockWidth).value_or(0) };
Display::Box::VerticalEdges collapsedMargin = { MarginCollapse::marginTop(layoutContext, layoutBox), MarginCollapse::marginBottom(layoutContext, layoutBox) };
auto borderAndPaddingTop = displayBox.borderTop() + displayBox.paddingTop();
if (!style.logicalHeight().isAuto())
return { style.logicalHeight().value(), nonCollapsedMargin, collapsedMargin };
if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
return { 0, nonCollapsedMargin, collapsedMargin };
if (layoutBox.establishesInlineFormattingContext()) {
return { 0, nonCollapsedMargin, collapsedMargin };
}
auto* lastInFlowChild = downcast<Container>(layoutBox).lastInFlowChild();
ASSERT(lastInFlowChild);
if (!MarginCollapse::isMarginBottomCollapsedWithParent(layoutContext, *lastInFlowChild)) {
auto* lastInFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*lastInFlowChild);
ASSERT(lastInFlowDisplayBox);
return { lastInFlowDisplayBox->bottom() + lastInFlowDisplayBox->marginBottom() - borderAndPaddingTop, nonCollapsedMargin, collapsedMargin };
}
auto* inFlowChild = lastInFlowChild;
while (inFlowChild && MarginCollapse::isMarginTopCollapsedWithParentMarginBottom(*inFlowChild))
inFlowChild = inFlowChild->previousInFlowSibling();
if (inFlowChild) {
auto* inFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*inFlowChild);
ASSERT(inFlowDisplayBox);
return { inFlowDisplayBox->top() + inFlowDisplayBox->borderBox().height() - borderAndPaddingTop, nonCollapsedMargin, collapsedMargin };
}
return { 0, nonCollapsedMargin, collapsedMargin };
};
auto heightAndMargin = compute();
if (!isStretchedToViewport(layoutContext, layoutBox)) {
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.margin.top << "px, " << heightAndMargin.margin.bottom << "px) -> layoutBox(" << &layoutBox << ")");
return heightAndMargin;
}
auto initialContainingBlockHeight = layoutContext.displayBoxForLayoutBox(initialContainingBlock(layoutBox))->contentBoxHeight();
if (heightAndMargin.height + heightAndMargin.margin.top + heightAndMargin.margin.bottom < initialContainingBlockHeight)
heightAndMargin.height = initialContainingBlockHeight - heightAndMargin.margin.top - heightAndMargin.margin.bottom;
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> streched to viewport -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.margin.top << "px, " << heightAndMargin.margin.bottom << "px) -> layoutBox(" << &layoutBox << ")");
return heightAndMargin;
}
FormattingContext::Geometry::WidthAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox,
std::optional<LayoutUnit> precomputedWidth)
{
ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
auto compute = [&]() {
auto& style = layoutBox.style();
auto* containingBlock = layoutBox.containingBlock();
auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*containingBlock)->contentBoxWidth();
auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
auto width = FormattingContext::Geometry::computedValueIfNotAuto(precomputedWidth ? Length { precomputedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
auto marginLeft = FormattingContext::Geometry::computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
auto marginRight = FormattingContext::Geometry::computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
auto borderLeft = displayBox.borderLeft();
auto borderRight = displayBox.borderRight();
auto paddingLeft = displayBox.paddingLeft();
auto paddingRight = displayBox.paddingRight();
if (width) {
auto horizontalSpaceForMargin = containingBlockWidth - (marginLeft.value_or(0) + borderLeft + paddingLeft + *width + paddingRight + borderRight + marginRight.value_or(0));
if (horizontalSpaceForMargin < 0) {
marginLeft = marginLeft.value_or(0);
marginRight = marginRight.value_or(0);
}
}
if (width && marginLeft && marginRight) {
if (containingBlock->style().isLeftToRightDirection())
marginRight = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight);
else
marginLeft = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
}
if (!marginLeft && width && marginRight)
marginLeft = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
else if (marginLeft && !width && marginRight)
width = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + paddingRight + borderRight + *marginRight);
else if (marginLeft && width && !marginRight)
marginRight = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight);
if (!width) {
marginLeft = marginLeft.value_or(0);
marginRight = marginRight.value_or(0);
width = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + paddingRight + borderRight + *marginRight);
}
if (!marginLeft && !marginRight) {
auto horizontalSpaceForMargin = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight);
marginLeft = marginRight = horizontalSpaceForMargin / 2;
}
ASSERT(width);
ASSERT(marginLeft);
ASSERT(marginRight);
return FormattingContext::Geometry::WidthAndMargin { *width, { *marginLeft, *marginRight } };
};
auto widthAndMargin = compute();
if (!isStretchedToViewport(layoutContext, layoutBox)) {
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> width(" << widthAndMargin.width << "px) margin(" << widthAndMargin.margin.left << "px, " << widthAndMargin.margin.right << "px) -> layoutBox(" << &layoutBox << ")");
return widthAndMargin;
}
auto initialContainingBlockWidth = layoutContext.displayBoxForLayoutBox(initialContainingBlock(layoutBox))->contentBoxWidth();
auto horizontalMargins = widthAndMargin.margin.left + widthAndMargin.margin.right;
if (widthAndMargin.width + horizontalMargins < initialContainingBlockWidth)
widthAndMargin.width = initialContainingBlockWidth - horizontalMargins;
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> streched to viewport-> width(" << widthAndMargin.width << "px) margin(" << widthAndMargin.margin.left << "px, " << widthAndMargin.margin.right << "px) -> layoutBox(" << &layoutBox << ")");
return widthAndMargin;
}
FormattingContext::Geometry::WidthAndMargin BlockFormattingContext::Geometry::inFlowReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
{
ASSERT(layoutBox.isInFlow() && layoutBox.replaced());
auto width = FormattingContext::Geometry::inlineReplacedWidthAndMargin(layoutContext, layoutBox).width;
auto margin = inFlowNonReplacedWidthAndMargin(layoutContext, layoutBox, width).margin;
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << width << "px) margin(" << margin.left << "px, " << margin.left << "px) -> layoutBox(" << &layoutBox << ")");
return { width, margin };
}
FormattingContext::Geometry::Position BlockFormattingContext::Geometry::staticPosition(LayoutContext& layoutContext, const Box& layoutBox)
{
LayoutUnit top;
auto& containingBlockDisplayBox = *layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
auto& previousInFlowDisplayBox = *layoutContext.displayBoxForLayoutBox(*previousInFlowSibling);
top = previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.marginBottom();
} else
top = containingBlockDisplayBox.contentBoxTop();
auto left = containingBlockDisplayBox.contentBoxLeft();
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position] -> static -> top(" << top << "px) left(" << left << "px) layoutBox(" << &layoutBox << ")");
return { left, top };
}
FormattingContext::Geometry::Position BlockFormattingContext::Geometry::inFlowPositionedPosition(LayoutContext& layoutContext, const Box& layoutBox)
{
ASSERT(layoutBox.isInFlowPositioned());
auto& style = layoutBox.style();
auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
auto& containingBlock = *layoutBox.containingBlock();
auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(containingBlock)->contentBoxWidth();
auto top = FormattingContext::Geometry::computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
auto bottom = FormattingContext::Geometry::computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
if (!top && !bottom) {
top = bottom = { 0 };
} else if (!top) {
top = -*bottom;
} else if (!bottom) {
bottom = -*top;
} else {
bottom = std::nullopt;
}
auto left = FormattingContext::Geometry::computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
auto right = FormattingContext::Geometry::computedValueIfNotAuto(style.logicalRight(), containingBlockWidth);
if (!left && !right) {
left = right = { 0 };
} else if (!left) {
left = -*right;
} else if (!right) {
right = -*left;
} else {
auto isLeftToRightDirection = containingBlock.style().isLeftToRightDirection();
if (isLeftToRightDirection)
right = -*left;
else
left = std::nullopt;
}
ASSERT(!bottom || *top == -*bottom);
ASSERT(!left || *left == -*right);
auto newTopPosition = displayBox.top() + *top;
auto newLeftPosition = displayBox.left() + left.value_or(-*right);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position] -> positioned inflow -> top(" << newTopPosition << "px) left(" << newLeftPosition << "px) layoutBox(" << &layoutBox << ")");
return { newLeftPosition, newTopPosition };
}
FormattingContext::Geometry::HeightAndMargin BlockFormattingContext::Geometry::inFlowHeightAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
{
ASSERT(layoutBox.isInFlow());
if (!layoutBox.replaced())
return inFlowNonReplacedHeightAndMargin(layoutContext, layoutBox);
return FormattingContext::Geometry::inlineReplacedHeightAndMargin(layoutContext, layoutBox);
}
FormattingContext::Geometry::WidthAndMargin BlockFormattingContext::Geometry::inFlowWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
{
ASSERT(layoutBox.isInFlow());
if (!layoutBox.replaced())
return inFlowNonReplacedWidthAndMargin(layoutContext, layoutBox);
return inFlowReplacedWidthAndMargin(layoutContext, layoutBox);
}
bool BlockFormattingContext::Geometry::instrinsicWidthConstraintsNeedChildrenWidth(const Box& layoutBox)
{
if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
return false;
return layoutBox.style().width().isAuto();
}
FormattingContext::InstrinsicWidthConstraints BlockFormattingContext::Geometry::instrinsicWidthConstraints(LayoutContext& layoutContext, const Box& layoutBox)
{
auto& style = layoutBox.style();
if (auto width = FormattingContext::Geometry::fixedValue(style.logicalWidth()))
return { *width, *width };
if (!style.logicalWidth().isAuto())
return { };
if (!is<Container>(layoutBox))
return { };
LayoutUnit minimumIntrinsicWidth;
LayoutUnit maximumIntrinsicWidth;
for (auto& child : childrenOfType<Box>(downcast<Container>(layoutBox))) {
if (child.isOutOfFlowPositioned())
continue;
auto& formattingState = layoutContext.formattingStateForBox(child);
ASSERT(formattingState.isBlockFormattingState());
auto childInstrinsicWidthConstraints = formattingState.instrinsicWidthConstraints(child);
ASSERT(childInstrinsicWidthConstraints);
auto& style = child.style();
auto horizontalMarginBorderAndPadding = FormattingContext::Geometry::fixedValue(style.marginLeft()).value_or(0)
+ LayoutUnit { style.borderLeftWidth() }
+ FormattingContext::Geometry::fixedValue(style.paddingLeft()).value_or(0)
+ FormattingContext::Geometry::fixedValue(style.paddingRight()).value_or(0)
+ LayoutUnit { style.borderRightWidth() }
+ FormattingContext::Geometry::fixedValue(style.marginRight()).value_or(0);
minimumIntrinsicWidth = std::max(minimumIntrinsicWidth, childInstrinsicWidthConstraints->minimum + horizontalMarginBorderAndPadding);
maximumIntrinsicWidth = std::max(maximumIntrinsicWidth, childInstrinsicWidthConstraints->maximum + horizontalMarginBorderAndPadding);
}
return { minimumIntrinsicWidth, maximumIntrinsicWidth };
}
}
}
#endif