BlockFormattingContextGeometry.cpp [plain text]
#include "config.h"
#include "BlockFormattingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "FormattingContext.h"
#include "InlineFormattingState.h"
#include "LayoutChildIterator.h"
#include "Logging.h"
#include <wtf/text/TextStream.h>
namespace WebCore {
namespace Layout {
HeightAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedHeight)
{
ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
ASSERT(layoutBox.isOverflowVisible());
auto compute = [&]() -> HeightAndMargin {
auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
auto computedVerticalMargin = Geometry::computedVerticalMargin(layoutState, layoutBox);
auto nonCollapsedMargin = UsedVerticalMargin::NonCollapsedValues { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
auto borderAndPaddingTop = displayBox.borderTop() + displayBox.paddingTop().valueOr(0);
auto height = usedHeight ? usedHeight.value() : computedHeightValue(layoutState, layoutBox, HeightType::Normal);
if (height)
return { height.value(), nonCollapsedMargin };
if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
return { 0, nonCollapsedMargin };
if (layoutBox.establishesInlineFormattingContext()) {
auto& inlineRuns = downcast<InlineFormattingState>(layoutState.establishedFormattingState(layoutBox)).inlineRuns();
auto bottomEdge = inlineRuns.isEmpty() ? LayoutUnit() : inlineRuns.last().logicalBottom();
return { bottomEdge, nonCollapsedMargin };
}
auto* lastInFlowChild = downcast<Container>(layoutBox).lastInFlowChild();
ASSERT(lastInFlowChild);
if (!MarginCollapse::marginAfterCollapsesWithParentMarginAfter(layoutState, *lastInFlowChild)) {
auto& lastInFlowDisplayBox = layoutState.displayBoxForLayoutBox(*lastInFlowChild);
return { lastInFlowDisplayBox.bottom() + lastInFlowDisplayBox.marginAfter() - borderAndPaddingTop, nonCollapsedMargin };
}
auto* inFlowChild = lastInFlowChild;
while (inFlowChild && MarginCollapse::marginBeforeCollapsesWithParentMarginAfter(layoutState, *inFlowChild))
inFlowChild = inFlowChild->previousInFlowSibling();
if (inFlowChild) {
auto& inFlowDisplayBox = layoutState.displayBoxForLayoutBox(*inFlowChild);
return { inFlowDisplayBox.top() + inFlowDisplayBox.borderBox().height() - borderAndPaddingTop, nonCollapsedMargin };
}
return { 0, nonCollapsedMargin };
};
auto heightAndMargin = compute();
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.nonCollapsedMargin.before << "px, " << heightAndMargin.nonCollapsedMargin.after << "px) -> layoutBox(" << &layoutBox << ")");
return heightAndMargin;
}
WidthAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
{
ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
auto compute = [&]() {
auto& style = layoutBox.style();
auto* containingBlock = layoutBox.containingBlock();
auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*containingBlock).contentBoxWidth();
auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
auto width = computedValueIfNotAuto(usedWidth ? Length { usedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutState, layoutBox);
UsedHorizontalMargin usedHorizontalMargin;
auto borderLeft = displayBox.borderLeft();
auto borderRight = displayBox.borderRight();
auto paddingLeft = displayBox.paddingLeft().valueOr(0);
auto paddingRight = displayBox.paddingRight().valueOr(0);
if (width) {
auto horizontalSpaceForMargin = containingBlockWidth - (computedHorizontalMargin.start.valueOr(0) + borderLeft + paddingLeft + *width + paddingRight + borderRight + computedHorizontalMargin.end.valueOr(0));
if (horizontalSpaceForMargin < 0)
usedHorizontalMargin = { computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) };
}
if (width && computedHorizontalMargin.start && computedHorizontalMargin.end) {
if (containingBlock->style().isLeftToRightDirection()) {
usedHorizontalMargin.start = *computedHorizontalMargin.start;
usedHorizontalMargin.end = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight);
} else {
usedHorizontalMargin.end = *computedHorizontalMargin.end;
usedHorizontalMargin.start = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end);
}
}
if (!computedHorizontalMargin.start && width && computedHorizontalMargin.end) {
usedHorizontalMargin.end = *computedHorizontalMargin.end;
usedHorizontalMargin.start = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end);
} else if (computedHorizontalMargin.start && !width && computedHorizontalMargin.end) {
usedHorizontalMargin = { *computedHorizontalMargin.start, *computedHorizontalMargin.end };
width = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + paddingRight + borderRight + usedHorizontalMargin.end);
} else if (computedHorizontalMargin.start && width && !computedHorizontalMargin.end) {
usedHorizontalMargin.start = *computedHorizontalMargin.start;
usedHorizontalMargin.end = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight);
}
if (!width) {
usedHorizontalMargin = { computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) };
width = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + paddingRight + borderRight + usedHorizontalMargin.end);
}
if (!computedHorizontalMargin.start && !computedHorizontalMargin.end) {
auto horizontalSpaceForMargin = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight);
usedHorizontalMargin = { horizontalSpaceForMargin / 2, horizontalSpaceForMargin / 2 };
}
ASSERT(width);
return WidthAndMargin { *width, usedHorizontalMargin, computedHorizontalMargin };
};
auto widthAndMargin = compute();
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> width(" << widthAndMargin.width << "px) margin(" << widthAndMargin.usedMargin.start << "px, " << widthAndMargin.usedMargin.end << "px) -> layoutBox(" << &layoutBox << ")");
return widthAndMargin;
}
WidthAndMargin BlockFormattingContext::Geometry::inFlowReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
{
ASSERT(layoutBox.isInFlow() && layoutBox.replaced());
auto width = inlineReplacedWidthAndMargin(layoutState, layoutBox, usedWidth).width;
auto nonReplacedWidthAndMargin = inFlowNonReplacedWidthAndMargin(layoutState, layoutBox, width);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << width << "px) margin(" << nonReplacedWidthAndMargin.usedMargin.start << "px, " << nonReplacedWidthAndMargin.usedMargin.end << "px) -> layoutBox(" << &layoutBox << ")");
return { width, nonReplacedWidthAndMargin.usedMargin, nonReplacedWidthAndMargin.computedMargin };
}
Point BlockFormattingContext::Geometry::staticPosition(const LayoutState& layoutState, const Box& layoutBox)
{
LayoutUnit top;
auto& containingBlockDisplayBox = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock());
if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
auto& previousInFlowDisplayBox = layoutState.displayBoxForLayoutBox(*previousInFlowSibling);
top = previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.marginAfter();
} 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 };
}
HeightAndMargin BlockFormattingContext::Geometry::inFlowHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedHeight)
{
ASSERT(layoutBox.isInFlow());
if (layoutBox.replaced())
return inlineReplacedHeightAndMargin(layoutState, layoutBox, usedHeight);
HeightAndMargin heightAndMargin;
if (layoutBox.isOverflowVisible() && !layoutBox.isDocumentBox())
heightAndMargin = inFlowNonReplacedHeightAndMargin(layoutState, layoutBox, usedHeight);
else {
heightAndMargin = complicatedCases(layoutState, layoutBox, usedHeight);
}
if (!Quirks::needsStretching(layoutState, layoutBox))
return heightAndMargin;
heightAndMargin = Quirks::stretchedHeight(layoutState, layoutBox, heightAndMargin);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> streched to viewport -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.nonCollapsedMargin.before << "px, " << heightAndMargin.nonCollapsedMargin.after << "px) -> layoutBox(" << &layoutBox << ")");
return heightAndMargin;
}
WidthAndMargin BlockFormattingContext::Geometry::inFlowWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
{
ASSERT(layoutBox.isInFlow());
if (!layoutBox.replaced())
return inFlowNonReplacedWidthAndMargin(layoutState, layoutBox, usedWidth);
return inFlowReplacedWidthAndMargin(layoutState, layoutBox, usedWidth);
}
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(const LayoutState& layoutState, const Box& layoutBox)
{
auto& style = layoutBox.style();
if (auto width = 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 = layoutState.formattingStateForBox(child);
ASSERT(formattingState.isBlockFormattingState());
auto childInstrinsicWidthConstraints = formattingState.instrinsicWidthConstraints(child);
ASSERT(childInstrinsicWidthConstraints);
auto& style = child.style();
auto horizontalMarginBorderAndPadding = fixedValue(style.marginStart()).valueOr(0)
+ LayoutUnit { style.borderLeftWidth() }
+ fixedValue(style.paddingLeft()).valueOr(0)
+ fixedValue(style.paddingRight()).valueOr(0)
+ LayoutUnit { style.borderRightWidth() }
+ fixedValue(style.marginEnd()).valueOr(0);
minimumIntrinsicWidth = std::max(minimumIntrinsicWidth, childInstrinsicWidthConstraints->minimum + horizontalMarginBorderAndPadding);
maximumIntrinsicWidth = std::max(maximumIntrinsicWidth, childInstrinsicWidthConstraints->maximum + horizontalMarginBorderAndPadding);
}
return { minimumIntrinsicWidth, maximumIntrinsicWidth };
}
LayoutUnit BlockFormattingContext::Geometry::estimatedMarginBefore(const LayoutState& layoutState, const Box& layoutBox)
{
ASSERT(layoutBox.isBlockLevelBox());
ASSERT(layoutBox.isInFlow());
ASSERT(!layoutBox.establishesBlockFormattingContext());
return MarginCollapse::marginBefore(layoutState, layoutBox);
}
LayoutUnit BlockFormattingContext::Geometry::estimatedMarginAfter(const LayoutState& layoutState, const Box& layoutBox)
{
ASSERT(layoutBox.isBlockLevelBox());
ASSERT(layoutBox.isInFlow());
ASSERT(!layoutBox.establishesBlockFormattingContext());
return MarginCollapse::marginAfter(layoutState, layoutBox);
}
}
}
#endif