BlockFormattingContextGeometry.cpp [plain text]
#include "config.h"
#include "BlockFormattingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "BlockFormattingState.h"
#include "FormattingContext.h"
#include "InlineFormattingState.h"
#include "LayoutBoxGeometry.h"
#include "LayoutChildIterator.h"
#include "LayoutContext.h"
#include "LayoutInitialContainingBlock.h"
#include "LayoutReplacedBox.h"
#include "Logging.h"
#include <wtf/text/TextStream.h>
namespace WebCore {
namespace Layout {
ContentHeightAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedContentHeightAndMargin(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints, const OverriddenVerticalValues& overriddenVerticalValues)
{
ASSERT(layoutBox.isInFlow() && !layoutBox.isReplacedBox());
ASSERT(layoutBox.isOverflowVisible());
auto compute = [&](const auto& overriddenVerticalValues) -> ContentHeightAndMargin {
auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
auto computedVerticalMargin = Geometry::computedVerticalMargin(layoutBox, horizontalConstraints);
auto nonCollapsedMargin = UsedVerticalMargin::NonCollapsedValues { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
auto borderAndPaddingTop = boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0);
auto height = overriddenVerticalValues.height ? overriddenVerticalValues.height.value() : computedHeight(layoutBox);
if (height)
return { *height, nonCollapsedMargin };
if (!is<ContainerBox>(layoutBox) || !downcast<ContainerBox>(layoutBox).hasInFlowChild())
return { 0, nonCollapsedMargin };
auto& layoutContainer = downcast<ContainerBox>(layoutBox);
if (layoutContainer.establishesInlineFormattingContext()) {
auto& inlineFormattingState = layoutState().establishedInlineFormattingState(layoutContainer);
auto& lines = inlineFormattingState.lines();
ASSERT(!lines.isEmpty());
return { toLayoutUnit(lines.last().lineBoxLogicalRect().bottom() + inlineFormattingState.clearGapAfterLastLine()) - borderAndPaddingTop, nonCollapsedMargin };
}
auto& lastInFlowChild = *layoutContainer.lastInFlowChild();
if (!formattingContext().marginCollapse().marginAfterCollapsesWithParentMarginAfter(lastInFlowChild)) {
auto& lastInFlowBoxGeometry = formattingContext().geometryForBox(lastInFlowChild);
auto bottomEdgeOfBottomMargin = BoxGeometry::borderBoxRect(lastInFlowBoxGeometry).bottom() + lastInFlowBoxGeometry.marginAfter();
return { bottomEdgeOfBottomMargin - borderAndPaddingTop, nonCollapsedMargin };
}
auto* inFlowChild = &lastInFlowChild;
while (inFlowChild && formattingContext().marginCollapse().marginBeforeCollapsesWithParentMarginAfter(*inFlowChild))
inFlowChild = inFlowChild->previousInFlowSibling();
if (inFlowChild) {
auto& inFlowBoxGeometry = formattingContext().geometryForBox(*inFlowChild);
return { BoxGeometry::borderBoxTop(inFlowBoxGeometry) + inFlowBoxGeometry.borderBox().height() - borderAndPaddingTop, nonCollapsedMargin };
}
return { 0, nonCollapsedMargin };
};
auto isAutoHeight = !overriddenVerticalValues.height && !computedHeight(layoutBox);
if (isAutoHeight && (layoutBox.establishesFormattingContext() && !layoutBox.establishesInlineFormattingContext()))
return compute( OverriddenVerticalValues { contentHeightForFormattingContextRoot(downcast<ContainerBox>(layoutBox)) });
return compute(overriddenVerticalValues);
}
ContentWidthAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedContentWidthAndMargin(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints, const OverriddenHorizontalValues& overriddenHorizontalValues)
{
ASSERT(layoutBox.isInFlow());
auto compute = [&]() {
auto containingBlockWidth = horizontalConstraints.logicalWidth;
auto& containingBlockStyle = layoutBox.containingBlock().style();
auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
auto width = overriddenHorizontalValues.width ? overriddenHorizontalValues.width : computedWidth(layoutBox, containingBlockWidth);
auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutBox, horizontalConstraints);
UsedHorizontalMargin usedHorizontalMargin;
auto borderLeft = boxGeometry.borderLeft();
auto borderRight = boxGeometry.borderRight();
auto paddingLeft = boxGeometry.paddingLeft().valueOr(0);
auto paddingRight = boxGeometry.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 (containingBlockStyle.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 };
}
auto shouldApplyCenterAlignForBlockContent = containingBlockStyle.textAlign() == TextAlignMode::WebKitCenter && (computedHorizontalMargin.start || computedHorizontalMargin.end);
if (shouldApplyCenterAlignForBlockContent) {
auto borderBoxWidth = (borderLeft + paddingLeft + *width + paddingRight + borderRight);
auto marginStart = computedHorizontalMargin.start.valueOr(0);
auto marginEnd = computedHorizontalMargin.end.valueOr(0);
auto centeredLogicalLeftForMarginBox = std::max((containingBlockWidth - borderBoxWidth - marginStart - marginEnd) / 2, 0_lu);
usedHorizontalMargin.start = centeredLogicalLeftForMarginBox + marginStart;
usedHorizontalMargin.end = containingBlockWidth - borderBoxWidth - marginStart + marginEnd;
}
ASSERT(width);
return ContentWidthAndMargin { *width, usedHorizontalMargin };
};
auto contentWidthAndMargin = compute();
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> width(" << contentWidthAndMargin.contentWidth << "px) margin(" << contentWidthAndMargin.usedMargin.start << "px, " << contentWidthAndMargin.usedMargin.end << "px) -> layoutBox(" << &layoutBox << ")");
return contentWidthAndMargin;
}
ContentWidthAndMargin BlockFormattingContext::Geometry::inFlowReplacedContentWidthAndMargin(const ReplacedBox& replacedBox, const HorizontalConstraints& horizontalConstraints, const OverriddenHorizontalValues& overriddenHorizontalValues)
{
ASSERT(replacedBox.isInFlow());
auto usedWidth = inlineReplacedContentWidthAndMargin(replacedBox, horizontalConstraints, { }, overriddenHorizontalValues).contentWidth;
auto nonReplacedWidthAndMargin = inFlowNonReplacedContentWidthAndMargin(replacedBox, horizontalConstraints, OverriddenHorizontalValues { usedWidth, overriddenHorizontalValues.margin });
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << usedWidth << "px) margin(" << nonReplacedWidthAndMargin.usedMargin.start << "px, " << nonReplacedWidthAndMargin.usedMargin.end << "px) -> layoutBox(" << &replacedBox << ")");
return { usedWidth, nonReplacedWidthAndMargin.usedMargin };
}
LayoutUnit BlockFormattingContext::Geometry::staticVerticalPosition(const Box& layoutBox, const VerticalConstraints& verticalConstraints) const
{
if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
auto& previousInFlowBoxGeometry = formattingContext().geometryForBox(*previousInFlowSibling);
return BoxGeometry::borderBoxRect(previousInFlowBoxGeometry).bottom() + previousInFlowBoxGeometry.marginAfter();
}
return verticalConstraints.logicalTop;
}
LayoutUnit BlockFormattingContext::Geometry::staticHorizontalPosition(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints) const
{
return horizontalConstraints.logicalLeft + formattingContext().geometryForBox(layoutBox).marginStart();
}
Point BlockFormattingContext::Geometry::staticPosition(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints, const VerticalConstraints& verticalConstraints) const
{
return { staticHorizontalPosition(layoutBox, horizontalConstraints), staticVerticalPosition(layoutBox, verticalConstraints) };
}
ContentHeightAndMargin BlockFormattingContext::Geometry::inFlowContentHeightAndMargin(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints, const OverriddenVerticalValues& overriddenVerticalValues)
{
ASSERT(layoutBox.isInFlow());
if (layoutBox.isReplacedBox())
return inlineReplacedContentHeightAndMargin(downcast<ReplacedBox>(layoutBox), horizontalConstraints, { }, overriddenVerticalValues);
ContentHeightAndMargin contentHeightAndMargin;
if (layoutBox.isOverflowVisible() && !layoutBox.isDocumentBox()) {
contentHeightAndMargin = inFlowNonReplacedContentHeightAndMargin(layoutBox, horizontalConstraints, overriddenVerticalValues);
} else {
contentHeightAndMargin = complicatedCases(layoutBox, horizontalConstraints, overriddenVerticalValues);
}
auto quirks = formattingContext().quirks();
if (!quirks.needsStretching(layoutBox))
return contentHeightAndMargin;
contentHeightAndMargin.contentHeight = quirks.stretchedInFlowHeight(layoutBox, contentHeightAndMargin);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> streched to viewport -> height(" << contentHeightAndMargin.contentHeight << "px) margin(" << contentHeightAndMargin.nonCollapsedMargin.before << "px, " << contentHeightAndMargin.nonCollapsedMargin.after << "px) -> layoutBox(" << &layoutBox << ")");
return contentHeightAndMargin;
}
ContentWidthAndMargin BlockFormattingContext::Geometry::inFlowContentWidthAndMargin(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints, const OverriddenHorizontalValues& overriddenHorizontalValues)
{
ASSERT(layoutBox.isInFlow());
if (!layoutBox.isReplacedBox())
return inFlowNonReplacedContentWidthAndMargin(layoutBox, horizontalConstraints, overriddenHorizontalValues);
return inFlowReplacedContentWidthAndMargin(downcast<ReplacedBox>(layoutBox), horizontalConstraints, overriddenHorizontalValues);
}
ContentWidthAndMargin BlockFormattingContext::Geometry::computedContentWidthAndMargin(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints, Optional<LayoutUnit> availableWidthFloatAvoider)
{
auto compute = [&] (auto constraintsForWidth, Optional<LayoutUnit> usedWidth) {
if (layoutBox.isFloatingPositioned())
return floatingContentWidthAndMargin(layoutBox, constraintsForWidth, { usedWidth, { } });
if (layoutBox.isInFlow())
return inFlowContentWidthAndMargin(layoutBox, constraintsForWidth, { usedWidth, { } });
ASSERT_NOT_REACHED();
return ContentWidthAndMargin { };
};
auto horizontalConstraintsForWidth = horizontalConstraints;
if (layoutBox.style().logicalWidth().isAuto() && availableWidthFloatAvoider) {
horizontalConstraintsForWidth.logicalWidth = *availableWidthFloatAvoider;
}
auto contentWidthAndMargin = compute(horizontalConstraintsForWidth, { });
auto availableWidth = horizontalConstraints.logicalWidth;
if (auto maxWidth = computedMaxWidth(layoutBox, availableWidth)) {
auto maxWidthAndMargin = compute(horizontalConstraints, maxWidth);
if (contentWidthAndMargin.contentWidth > maxWidthAndMargin.contentWidth)
contentWidthAndMargin = maxWidthAndMargin;
}
auto minWidth = computedMinWidth(layoutBox, availableWidth).valueOr(0);
auto minWidthAndMargin = compute(horizontalConstraints, minWidth);
if (contentWidthAndMargin.contentWidth < minWidthAndMargin.contentWidth)
contentWidthAndMargin = minWidthAndMargin;
return contentWidthAndMargin;
}
FormattingContext::IntrinsicWidthConstraints BlockFormattingContext::Geometry::intrinsicWidthConstraints(const Box& layoutBox)
{
auto fixedMarginBorderAndPadding = [&](auto& layoutBox) {
auto& style = layoutBox.style();
return 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);
};
auto computedIntrinsicWidthConstraints = [&]() -> IntrinsicWidthConstraints {
auto logicalWidth = layoutBox.style().logicalWidth();
auto needsResolvedContainingBlockWidth = logicalWidth.isCalculated() || logicalWidth.isPercent() || logicalWidth.isRelative();
if (needsResolvedContainingBlockWidth)
return { };
if (auto width = fixedValue(logicalWidth))
return { *width, *width };
if (layoutBox.isReplacedBox()) {
auto& replacedBox = downcast<ReplacedBox>(layoutBox);
if (replacedBox.hasIntrinsicWidth()) {
auto replacedWidth = replacedBox.intrinsicWidth();
return { replacedWidth, replacedWidth };
}
return { };
}
if (!is<ContainerBox>(layoutBox) || !downcast<ContainerBox>(layoutBox).hasInFlowOrFloatingChild())
return { };
if (layoutBox.establishesFormattingContext()) {
auto intrinsicWidthConstraints = LayoutContext::createFormattingContext(downcast<ContainerBox>(layoutBox), layoutState())->computedIntrinsicWidthConstraints();
if (logicalWidth.isMinContent())
return { intrinsicWidthConstraints.minimum, intrinsicWidthConstraints.minimum };
if (logicalWidth.isMaxContent())
return { intrinsicWidthConstraints.maximum, intrinsicWidthConstraints.maximum };
return intrinsicWidthConstraints;
}
auto intrinsicWidthConstraints = IntrinsicWidthConstraints { };
auto& formattingState = layoutState().formattingStateForBox(layoutBox);
for (auto& child : childrenOfType<Box>(downcast<ContainerBox>(layoutBox))) {
if (child.isOutOfFlowPositioned() || (child.isFloatAvoider() && !child.hasFloatClear()))
continue;
auto childIntrinsicWidthConstraints = formattingState.intrinsicWidthConstraintsForBox(child);
ASSERT(childIntrinsicWidthConstraints);
intrinsicWidthConstraints.minimum = std::max(intrinsicWidthConstraints.minimum, childIntrinsicWidthConstraints->minimum);
intrinsicWidthConstraints.maximum = std::max(intrinsicWidthConstraints.maximum, childIntrinsicWidthConstraints->maximum);
}
return intrinsicWidthConstraints;
};
auto intrinsicWidthConstraints = constrainByMinMaxWidth(layoutBox, computedIntrinsicWidthConstraints());
intrinsicWidthConstraints.expand(fixedMarginBorderAndPadding(layoutBox));
return intrinsicWidthConstraints;
}
}
}
#endif