FormattingContextGeometry.cpp [plain text]
#include "config.h"
#include "FormattingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "FloatingState.h"
#include "FormattingState.h"
#include "InlineFormattingState.h"
namespace WebCore {
namespace Layout {
static inline bool isHeightAuto(const Box& layoutBox)
{
auto height = layoutBox.style().logicalHeight();
if (height.isAuto())
return true;
if (height.isPercent()) {
if (layoutBox.isOutOfFlowPositioned())
return false;
return !layoutBox.containingBlock()->style().logicalHeight().isFixed();
}
return false;
}
Optional<LayoutUnit> FormattingContext::Geometry::computedHeightValue(const LayoutState& layoutState, const Box& layoutBox, HeightType heightType)
{
auto& style = layoutBox.style();
auto height = heightType == HeightType::Normal ? style.logicalHeight() : heightType == HeightType::Min ? style.logicalMinHeight() : style.logicalMaxHeight();
if (height.isUndefined() || height.isAuto())
return { };
if (height.isFixed())
return { height.value() };
Optional<LayoutUnit> containingBlockHeightValue;
if (layoutBox.isOutOfFlowPositioned()) {
containingBlockHeightValue = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).height();
} else {
if (layoutState.inQuirksMode())
containingBlockHeightValue = FormattingContext::Quirks::heightValueOfNearestContainingBlockWithFixedHeight(layoutState, layoutBox);
else {
auto containingBlockHeight = layoutBox.containingBlock()->style().logicalHeight();
if (containingBlockHeight.isFixed())
containingBlockHeightValue = { containingBlockHeight.value() };
}
}
if (!containingBlockHeightValue)
return { };
return valueForLength(height, *containingBlockHeightValue);
}
static LayoutUnit contentHeightForFormattingContextRoot(const LayoutState& layoutState, const Box& layoutBox)
{
ASSERT(isHeightAuto(layoutBox) && (layoutBox.establishesFormattingContext() || layoutBox.isDocumentBox()));
if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
return 0;
LayoutUnit top;
LayoutUnit bottom;
auto& formattingRootContainer = downcast<Container>(layoutBox);
if (formattingRootContainer.establishesInlineFormattingContext()) {
auto& inlineRuns = downcast<InlineFormattingState>(layoutState.establishedFormattingState(layoutBox)).inlineRuns();
if (!inlineRuns.isEmpty()) {
top = inlineRuns[0].logicalTop();
bottom = inlineRuns.last().logicalBottom();
}
} else if (formattingRootContainer.establishesBlockFormattingContext() || layoutBox.isDocumentBox()) {
auto& firstDisplayBox = layoutState.displayBoxForLayoutBox(*formattingRootContainer.firstInFlowChild());
auto& lastDisplayBox = layoutState.displayBoxForLayoutBox(*formattingRootContainer.lastInFlowChild());
top = firstDisplayBox.rectWithMargin().top();
bottom = lastDisplayBox.rectWithMargin().bottom();
}
auto* formattingContextRoot = &layoutBox;
if (!layoutBox.establishesFormattingContext()) {
ASSERT(layoutBox.isDocumentBox());
formattingContextRoot = &layoutBox.formattingContextRoot();
}
auto floatsBottom = layoutState.establishedFormattingState(*formattingContextRoot).floatingState().bottom(*formattingContextRoot);
if (floatsBottom)
bottom = std::max<LayoutUnit>(*floatsBottom, bottom);
auto computedHeight = bottom - top;
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height] -> content height for formatting context root -> height(" << computedHeight << "px) layoutBox("<< &layoutBox << ")");
return computedHeight;
}
Optional<LayoutUnit> FormattingContext::Geometry::computedValueIfNotAuto(const Length& geometryProperty, LayoutUnit containingBlockWidth)
{
if (geometryProperty.isUndefined())
return WTF::nullopt;
if (geometryProperty.isAuto())
return WTF::nullopt;
return valueForLength(geometryProperty, containingBlockWidth);
}
Optional<LayoutUnit> FormattingContext::Geometry::fixedValue(const Length& geometryProperty)
{
if (!geometryProperty.isFixed())
return WTF::nullopt;
return { geometryProperty.value() };
}
Optional<LayoutUnit> FormattingContext::Geometry::computedMaxHeight(const LayoutState& layoutState, const Box& layoutBox)
{
return computedHeightValue(layoutState, layoutBox, HeightType::Max);
}
Optional<LayoutUnit> FormattingContext::Geometry::computedMinHeight(const LayoutState& layoutState, const Box& layoutBox)
{
if (auto minHeightValue = computedHeightValue(layoutState, layoutBox, HeightType::Min))
return minHeightValue;
return { 0 };
}
static LayoutUnit staticVerticalPositionForOutOfFlowPositioned(const LayoutState& layoutState, const Box& layoutBox)
{
ASSERT(layoutBox.isOutOfFlowPositioned());
LayoutUnit top;
if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
auto& previousInFlowDisplayBox = layoutState.displayBoxForLayoutBox(*previousInFlowSibling);
top += previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.nonCollapsedMarginAfter();
} else {
ASSERT(layoutBox.parent());
top = layoutState.displayBoxForLayoutBox(*layoutBox.parent()).contentBoxTop();
}
auto* containingBlock = layoutBox.containingBlock();
for (auto* container = layoutBox.parent(); container != containingBlock; container = container->containingBlock()) {
auto& displayBox = layoutState.displayBoxForLayoutBox(*container);
top += displayBox.top();
ASSERT(!container->isPositioned());
}
return top;
}
static LayoutUnit staticHorizontalPositionForOutOfFlowPositioned(const LayoutState& layoutState, const Box& layoutBox)
{
ASSERT(layoutBox.isOutOfFlowPositioned());
ASSERT(layoutBox.parent());
auto left = layoutState.displayBoxForLayoutBox(*layoutBox.parent()).contentBoxLeft();
auto* containingBlock = layoutBox.containingBlock();
for (auto* container = layoutBox.parent(); container != containingBlock; container = container->containingBlock()) {
auto& displayBox = layoutState.displayBoxForLayoutBox(*container);
left += displayBox.left();
ASSERT(!container->isPositioned());
}
return left;
}
LayoutUnit FormattingContext::Geometry::shrinkToFitWidth(LayoutState& layoutState, const Box& formattingRoot)
{
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width] -> shrink to fit -> unsupported -> width(" << LayoutUnit { } << "px) layoutBox: " << &formattingRoot << ")");
ASSERT(formattingRoot.establishesFormattingContext());
auto availableWidth = layoutState.displayBoxForLayoutBox(*formattingRoot.containingBlock()).width();
auto& formattingState = layoutState.createFormattingStateForFormattingRootIfNeeded(formattingRoot);
auto instrinsicWidthConstraints = formattingState.createFormattingContext(formattingRoot)->instrinsicWidthConstraints();
return std::min(std::max(instrinsicWidthConstraints.minimum, availableWidth), instrinsicWidthConstraints.maximum);
}
VerticalGeometry FormattingContext::Geometry::outOfFlowNonReplacedVerticalGeometry(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedHeight)
{
ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
auto& style = layoutBox.style();
auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
auto& containingBlockDisplayBox = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock());
auto containingBlockHeight = containingBlockDisplayBox.height();
auto containingBlockWidth = containingBlockDisplayBox.width();
auto top = computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
auto bottom = computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
auto height = usedHeight ? usedHeight.value() : computedHeightValue(layoutState, layoutBox, HeightType::Normal);
auto computedVerticalMargin = Geometry::computedVerticalMargin(layoutState, layoutBox);
UsedVerticalMargin::NonCollapsedValues usedVerticalMargin;
auto paddingTop = displayBox.paddingTop().valueOr(0);
auto paddingBottom = displayBox.paddingBottom().valueOr(0);
auto borderTop = displayBox.borderTop();
auto borderBottom = displayBox.borderBottom();
if (!top && !height && !bottom)
top = staticVerticalPositionForOutOfFlowPositioned(layoutState, layoutBox);
if (top && height && bottom) {
if (!computedVerticalMargin.before && !computedVerticalMargin.after) {
auto marginBeforeAndAfter = containingBlockHeight - (*top + borderTop + paddingTop + *height + paddingBottom + borderBottom + *bottom);
usedVerticalMargin = { marginBeforeAndAfter / 2, marginBeforeAndAfter / 2 };
} else if (!computedVerticalMargin.before) {
usedVerticalMargin.after = *computedVerticalMargin.after;
usedVerticalMargin.before = containingBlockHeight - (*top + borderTop + paddingTop + *height + paddingBottom + borderBottom + usedVerticalMargin.after + *bottom);
} else {
usedVerticalMargin.before = *computedVerticalMargin.before;
usedVerticalMargin.after = containingBlockHeight - (*top + usedVerticalMargin.before + borderTop + paddingTop + *height + paddingBottom + borderBottom + *bottom);
}
auto boxHeight = *top + usedVerticalMargin.before + borderTop + paddingTop + *height + paddingBottom + borderBottom + usedVerticalMargin.after + *bottom;
if (boxHeight > containingBlockHeight)
bottom = containingBlockHeight - (*top + usedVerticalMargin.before + borderTop + paddingTop + *height + paddingBottom + borderBottom + usedVerticalMargin.after);
}
if (!top && !height && bottom) {
height = contentHeightForFormattingContextRoot(layoutState, layoutBox);
usedVerticalMargin = { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
top = containingBlockHeight - (usedVerticalMargin.before + borderTop + paddingTop + *height + paddingBottom + borderBottom + usedVerticalMargin.after + *bottom);
}
if (!top && !bottom && height) {
top = staticVerticalPositionForOutOfFlowPositioned(layoutState, layoutBox);
usedVerticalMargin = { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
bottom = containingBlockHeight - (*top + usedVerticalMargin.before + borderTop + paddingTop + *height + paddingBottom + borderBottom + usedVerticalMargin.after);
}
if (!height && !bottom && top) {
height = contentHeightForFormattingContextRoot(layoutState, layoutBox);
usedVerticalMargin = { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
bottom = containingBlockHeight - (*top + usedVerticalMargin.before + borderTop + paddingTop + *height + paddingBottom + borderBottom + usedVerticalMargin.after);
}
if (!top && height && bottom) {
usedVerticalMargin = { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
top = containingBlockHeight - (usedVerticalMargin.before + borderTop + paddingTop + *height + paddingBottom + borderBottom + usedVerticalMargin.after + *bottom);
}
if (!height && top && bottom) {
usedVerticalMargin = { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
height = containingBlockHeight - (*top + usedVerticalMargin.before + borderTop + paddingTop + paddingBottom + borderBottom + usedVerticalMargin.after + *bottom);
}
if (!bottom && top && height) {
usedVerticalMargin = { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
bottom = containingBlockHeight - (*top + usedVerticalMargin.before + borderTop + paddingTop + *height + paddingBottom + borderBottom + usedVerticalMargin.after);
}
ASSERT(top);
ASSERT(bottom);
ASSERT(height);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Height][Margin] -> out-of-flow non-replaced -> top(" << *top << "px) bottom(" << *bottom << "px) height(" << *height << "px) margin(" << usedVerticalMargin.before << "px, " << usedVerticalMargin.after << "px) layoutBox(" << &layoutBox << ")");
return { *top, *bottom, { *height, usedVerticalMargin } };
}
HorizontalGeometry FormattingContext::Geometry::outOfFlowNonReplacedHorizontalGeometry(LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
{
ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
auto& style = layoutBox.style();
auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
auto& containingBlock = *layoutBox.containingBlock();
auto containingBlockWidth = layoutState.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
auto isLeftToRightDirection = containingBlock.style().isLeftToRightDirection();
auto left = computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
auto right = computedValueIfNotAuto(style.logicalRight(), containingBlockWidth);
auto width = computedValueIfNotAuto(usedWidth ? Length { usedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutState, layoutBox);
UsedHorizontalMargin usedHorizontalMargin;
auto paddingLeft = displayBox.paddingLeft().valueOr(0);
auto paddingRight = displayBox.paddingRight().valueOr(0);
auto borderLeft = displayBox.borderLeft();
auto borderRight = displayBox.borderRight();
if (!left && !width && !right) {
usedHorizontalMargin = { computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) };
auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutState, layoutBox);
if (isLeftToRightDirection)
left = staticHorizontalPosition;
else
right = staticHorizontalPosition;
} else if (left && width && right) {
if (!computedHorizontalMargin.start && !computedHorizontalMargin.end) {
auto marginStartAndEnd = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
if (marginStartAndEnd >= 0)
usedHorizontalMargin = { marginStartAndEnd / 2, marginStartAndEnd / 2 };
else {
if (isLeftToRightDirection) {
usedHorizontalMargin.start = 0_lu;
usedHorizontalMargin.end = containingBlockWidth - (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
} else {
usedHorizontalMargin.end = 0_lu;
usedHorizontalMargin.start = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end + *right);
}
}
} else if (!computedHorizontalMargin.start) {
usedHorizontalMargin.end = *computedHorizontalMargin.end;
usedHorizontalMargin.start = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end + *right);
if (usedHorizontalMargin.start < 0) {
if (isLeftToRightDirection)
usedHorizontalMargin.start = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end);
else
usedHorizontalMargin.start = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end + *right);
}
} else if (!computedHorizontalMargin.end) {
usedHorizontalMargin.start = *computedHorizontalMargin.start;
usedHorizontalMargin.end = containingBlockWidth - (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
if (usedHorizontalMargin.end < 0) {
if (isLeftToRightDirection)
usedHorizontalMargin.end = containingBlockWidth - (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight);
else
usedHorizontalMargin.end = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
}
}
} else {
usedHorizontalMargin = { computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) };
}
if (!left && !width && right) {
width = shrinkToFitWidth(layoutState, layoutBox);
left = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end + *right);
} else if (!left && !right && width) {
auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutState, layoutBox);
if (isLeftToRightDirection) {
left = staticHorizontalPosition;
right = containingBlockWidth - (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end);
} else {
right = staticHorizontalPosition;
left = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end + *right);
}
} else if (!width && !right && left) {
width = shrinkToFitWidth(layoutState, layoutBox);
right = containingBlockWidth - (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end);
} else if (!left && width && right) {
left = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end + *right);
} else if (!width && left && right) {
width = containingBlockWidth - (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + paddingRight + borderRight + usedHorizontalMargin.end + *right);
} else if (!right && left && width) {
right = containingBlockWidth - (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + *width + paddingRight + borderRight + usedHorizontalMargin.end);
}
ASSERT(left);
ASSERT(right);
ASSERT(width);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Width][Margin] -> out-of-flow non-replaced -> left(" << *left << "px) right(" << *right << "px) width(" << *width << "px) margin(" << usedHorizontalMargin.start << "px, " << usedHorizontalMargin.end << "px) layoutBox(" << &layoutBox << ")");
return { *left, *right, { *width, usedHorizontalMargin, computedHorizontalMargin } };
}
VerticalGeometry FormattingContext::Geometry::outOfFlowReplacedVerticalGeometry(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedHeight)
{
ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
auto& style = layoutBox.style();
auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
auto& containingBlockDisplayBox = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock());
auto containingBlockHeight = containingBlockDisplayBox.height();
auto containingBlockWidth = containingBlockDisplayBox.width();
auto top = computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
auto bottom = computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
auto height = inlineReplacedHeightAndMargin(layoutState, layoutBox, usedHeight).height;
auto computedVerticalMargin = Geometry::computedVerticalMargin(layoutState, layoutBox);
UsedVerticalMargin::NonCollapsedValues usedVerticalMargin;
auto paddingTop = displayBox.paddingTop().valueOr(0);
auto paddingBottom = displayBox.paddingBottom().valueOr(0);
auto borderTop = displayBox.borderTop();
auto borderBottom = displayBox.borderBottom();
if (!top && !bottom) {
top = staticVerticalPositionForOutOfFlowPositioned(layoutState, layoutBox);
}
if (!bottom) {
usedVerticalMargin = { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
}
if (!computedVerticalMargin.before && !computedVerticalMargin.after) {
auto marginBeforeAndAfter = containingBlockHeight - (*top + borderTop + paddingTop + height + paddingBottom + borderBottom + *bottom);
usedVerticalMargin = { marginBeforeAndAfter / 2, marginBeforeAndAfter / 2 };
}
if (!top)
top = containingBlockHeight - (usedVerticalMargin.before + borderTop + paddingTop + height + paddingBottom + borderBottom + usedVerticalMargin.after + *bottom);
if (!bottom)
bottom = containingBlockHeight - (*top + usedVerticalMargin.before + borderTop + paddingTop + height + paddingBottom + borderBottom + usedVerticalMargin.after);
if (!computedVerticalMargin.before)
usedVerticalMargin.before = containingBlockHeight - (*top + borderTop + paddingTop + height + paddingBottom + borderBottom + usedVerticalMargin.after + *bottom);
if (!computedVerticalMargin.after)
usedVerticalMargin.after = containingBlockHeight - (*top + usedVerticalMargin.before + borderTop + paddingTop + height + paddingBottom + borderBottom + *bottom);
auto boxHeight = *top + usedVerticalMargin.before + borderTop + paddingTop + height + paddingBottom + borderBottom + usedVerticalMargin.after + *bottom;
if (boxHeight > containingBlockHeight)
bottom = containingBlockHeight - (*top + usedVerticalMargin.before + borderTop + paddingTop + height + paddingBottom + borderBottom + usedVerticalMargin.after);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Height][Margin] -> out-of-flow replaced -> top(" << *top << "px) bottom(" << *bottom << "px) height(" << height << "px) margin(" << usedVerticalMargin.before << "px, " << usedVerticalMargin.after << "px) layoutBox(" << &layoutBox << ")");
return { *top, *bottom, { height, usedVerticalMargin } };
}
HorizontalGeometry FormattingContext::Geometry::outOfFlowReplacedHorizontalGeometry(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
{
ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
auto& style = layoutBox.style();
auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
auto& containingBlock = *layoutBox.containingBlock();
auto containingBlockWidth = layoutState.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
auto isLeftToRightDirection = containingBlock.style().isLeftToRightDirection();
auto left = computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
auto right = computedValueIfNotAuto(style.logicalRight(), containingBlockWidth);
auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutState, layoutBox);
UsedHorizontalMargin usedHorizontalMargin;
auto width = inlineReplacedWidthAndMargin(layoutState, layoutBox, usedWidth).width;
auto paddingLeft = displayBox.paddingLeft().valueOr(0);
auto paddingRight = displayBox.paddingRight().valueOr(0);
auto borderLeft = displayBox.borderLeft();
auto borderRight = displayBox.borderRight();
if (!left && !right) {
auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutState, layoutBox);
if (isLeftToRightDirection)
left = staticHorizontalPosition;
else
right = staticHorizontalPosition;
}
if (!left || !right) {
usedHorizontalMargin = { computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) };
}
if (!computedHorizontalMargin.start && !computedHorizontalMargin.end) {
auto marginStartAndEnd = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
if (marginStartAndEnd >= 0)
usedHorizontalMargin = { marginStartAndEnd / 2, marginStartAndEnd / 2 };
else {
if (isLeftToRightDirection) {
usedHorizontalMargin.start = 0_lu;
usedHorizontalMargin.end = containingBlockWidth - (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
} else {
usedHorizontalMargin.end = 0_lu;
usedHorizontalMargin.start = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + usedHorizontalMargin.end + *right);
}
}
}
if (!left)
left = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + width + paddingRight + borderRight + usedHorizontalMargin.end + *right);
if (!right)
right = containingBlockWidth - (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + width + paddingRight + borderRight + usedHorizontalMargin.end);
if (!computedHorizontalMargin.start)
usedHorizontalMargin.start = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + usedHorizontalMargin.end + *right);
if (!computedHorizontalMargin.end)
usedHorizontalMargin.end = containingBlockWidth - (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
auto boxWidth = (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + width + paddingRight + borderRight + usedHorizontalMargin.end + *right);
if (boxWidth > containingBlockWidth) {
if (isLeftToRightDirection)
right = containingBlockWidth - (*left + usedHorizontalMargin.start + borderLeft + paddingLeft + width + paddingRight + borderRight + usedHorizontalMargin.end);
else
left = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + width + paddingRight + borderRight + usedHorizontalMargin.end + *right);
}
ASSERT(left);
ASSERT(right);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Width][Margin] -> out-of-flow replaced -> left(" << *left << "px) right(" << *right << "px) width(" << width << "px) margin(" << usedHorizontalMargin.start << "px, " << usedHorizontalMargin.end << "px) layoutBox(" << &layoutBox << ")");
return { *left, *right, { width, usedHorizontalMargin, computedHorizontalMargin } };
}
HeightAndMargin FormattingContext::Geometry::complicatedCases(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedHeight)
{
ASSERT(!layoutBox.replaced());
ASSERT((layoutBox.isBlockLevelBox() && layoutBox.isInFlow() && !layoutBox.isOverflowVisible()) || layoutBox.isInlineBlockBox() || layoutBox.isFloatingPositioned() || layoutBox.isDocumentBox());
auto height = usedHeight ? usedHeight.value() : computedHeightValue(layoutState, layoutBox, HeightType::Normal);
auto computedVerticalMargin = Geometry::computedVerticalMargin(layoutState, layoutBox);
auto usedVerticalMargin = UsedVerticalMargin::NonCollapsedValues { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
if (!height) {
ASSERT(isHeightAuto(layoutBox));
height = contentHeightForFormattingContextRoot(layoutState, layoutBox);
}
ASSERT(height);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating non-replaced -> height(" << *height << "px) margin(" << usedVerticalMargin.before << "px, " << usedVerticalMargin.after << "px) -> layoutBox(" << &layoutBox << ")");
return HeightAndMargin { *height, usedVerticalMargin };
}
WidthAndMargin FormattingContext::Geometry::floatingNonReplacedWidthAndMargin(LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
{
ASSERT(layoutBox.isFloatingPositioned() && !layoutBox.replaced());
auto& containingBlock = *layoutBox.containingBlock();
auto containingBlockWidth = layoutState.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutState, layoutBox);
auto usedHorizontallMargin = UsedHorizontalMargin { computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) };
auto width = computedValueIfNotAuto(usedWidth ? Length { usedWidth.value(), Fixed } : layoutBox.style().logicalWidth(), containingBlockWidth);
if (!width)
width = shrinkToFitWidth(layoutState, layoutBox);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> floating non-replaced -> width(" << *width << "px) margin(" << usedHorizontallMargin.start << "px, " << usedHorizontallMargin.end << "px) -> layoutBox(" << &layoutBox << ")");
return WidthAndMargin { *width, usedHorizontallMargin, computedHorizontalMargin };
}
HeightAndMargin FormattingContext::Geometry::floatingReplacedHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedHeight)
{
ASSERT(layoutBox.isFloatingPositioned() && layoutBox.replaced());
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating replaced -> redirected to inline replaced");
return inlineReplacedHeightAndMargin(layoutState, layoutBox, usedHeight);
}
WidthAndMargin FormattingContext::Geometry::floatingReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
{
ASSERT(layoutBox.isFloatingPositioned() && layoutBox.replaced());
auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutState, layoutBox);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating replaced -> redirected to inline replaced");
return inlineReplacedWidthAndMargin(layoutState, layoutBox, usedWidth, computedHorizontalMargin.start, computedHorizontalMargin.end);
}
VerticalGeometry FormattingContext::Geometry::outOfFlowVerticalGeometry(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedHeight)
{
ASSERT(layoutBox.isOutOfFlowPositioned());
if (!layoutBox.replaced())
return outOfFlowNonReplacedVerticalGeometry(layoutState, layoutBox, usedHeight);
return outOfFlowReplacedVerticalGeometry(layoutState, layoutBox, usedHeight);
}
HorizontalGeometry FormattingContext::Geometry::outOfFlowHorizontalGeometry(LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
{
ASSERT(layoutBox.isOutOfFlowPositioned());
if (!layoutBox.replaced())
return outOfFlowNonReplacedHorizontalGeometry(layoutState, layoutBox, usedWidth);
return outOfFlowReplacedHorizontalGeometry(layoutState, layoutBox, usedWidth);
}
HeightAndMargin FormattingContext::Geometry::floatingHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedHeight)
{
ASSERT(layoutBox.isFloatingPositioned());
if (!layoutBox.replaced())
return complicatedCases(layoutState, layoutBox, usedHeight);
return floatingReplacedHeightAndMargin(layoutState, layoutBox, usedHeight);
}
WidthAndMargin FormattingContext::Geometry::floatingWidthAndMargin(LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
{
ASSERT(layoutBox.isFloatingPositioned());
if (!layoutBox.replaced())
return floatingNonReplacedWidthAndMargin(layoutState, layoutBox, usedWidth);
return floatingReplacedWidthAndMargin(layoutState, layoutBox, usedWidth);
}
HeightAndMargin FormattingContext::Geometry::inlineReplacedHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedHeight)
{
ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
auto computedVerticalMargin = Geometry::computedVerticalMargin(layoutState, layoutBox);
auto usedVerticalMargin = UsedVerticalMargin::NonCollapsedValues { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
auto& style = layoutBox.style();
auto replaced = layoutBox.replaced();
auto height = usedHeight ? usedHeight.value() : computedHeightValue(layoutState, layoutBox, HeightType::Normal);
auto heightIsAuto = !usedHeight && isHeightAuto(layoutBox);
auto widthIsAuto = style.logicalWidth().isAuto();
if (heightIsAuto && widthIsAuto && replaced->hasIntrinsicHeight()) {
height = replaced->intrinsicHeight();
} else if (heightIsAuto && replaced->hasIntrinsicRatio()) {
auto usedWidth = layoutState.displayBoxForLayoutBox(layoutBox).width();
height = usedWidth / replaced->intrinsicRatio();
} else if (heightIsAuto && replaced->hasIntrinsicHeight()) {
height = replaced->intrinsicHeight();
} else if (heightIsAuto) {
height = { 150 };
}
ASSERT(height);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow replaced -> height(" << *height << "px) margin(" << usedVerticalMargin.before << "px, " << usedVerticalMargin.after << "px) -> layoutBox(" << &layoutBox << ")");
return { *height, usedVerticalMargin };
}
WidthAndMargin FormattingContext::Geometry::inlineReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox,
Optional<LayoutUnit> usedWidth, Optional<LayoutUnit> precomputedMarginStart, Optional<LayoutUnit> precomputedMarginEnd)
{
ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
auto& style = layoutBox.style();
auto& containingBlock = *layoutBox.containingBlock();
auto containingBlockWidth = layoutState.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutState, layoutBox);
auto usedMarginStart = [&] {
return precomputedMarginStart.valueOr(computedHorizontalMargin.start.valueOr(0_lu));
};
auto usedMarginEnd = [&] {
return precomputedMarginEnd.valueOr(computedHorizontalMargin.end.valueOr(0_lu));
};
auto replaced = layoutBox.replaced();
ASSERT(replaced);
auto width = computedValueIfNotAuto(usedWidth ? Length { usedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
auto heightIsAuto = isHeightAuto(layoutBox);
auto height = computedHeightValue(layoutState, layoutBox, HeightType::Normal);
if (!width && heightIsAuto && replaced->hasIntrinsicWidth()) {
width = replaced->intrinsicWidth();
} else if ((!width && heightIsAuto && !replaced->hasIntrinsicWidth() && replaced->hasIntrinsicHeight() && replaced->hasIntrinsicRatio())
|| (!width && height && replaced->hasIntrinsicRatio())) {
width = height.valueOr(replaced->hasIntrinsicHeight()) * replaced->intrinsicRatio();
} else if (!width && heightIsAuto && replaced->hasIntrinsicRatio() && !replaced->hasIntrinsicWidth() && !replaced->hasIntrinsicHeight()) {
ASSERT_NOT_IMPLEMENTED_YET();
} else if (!width && replaced->hasIntrinsicWidth()) {
width = replaced->intrinsicWidth();
} else if (!width) {
width = { 300 };
}
ASSERT(width);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << *width << "px) margin(" << usedMarginStart() << "px, " << usedMarginEnd() << "px) -> layoutBox(" << &layoutBox << ")");
return { *width, { usedMarginStart(), usedMarginEnd() }, computedHorizontalMargin };
}
LayoutSize FormattingContext::Geometry::inFlowPositionedPositionOffset(const LayoutState& layoutState, const Box& layoutBox)
{
ASSERT(layoutBox.isInFlowPositioned());
auto& style = layoutBox.style();
auto& containingBlock = *layoutBox.containingBlock();
auto containingBlockWidth = layoutState.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
auto top = computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
auto bottom = computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
if (!top && !bottom) {
top = bottom = { 0 };
} else if (!top) {
top = -*bottom;
} else if (!bottom) {
bottom = -*top;
} else {
bottom = WTF::nullopt;
}
auto left = computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
auto right = 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 = WTF::nullopt;
}
ASSERT(!bottom || *top == -*bottom);
ASSERT(!left || *left == -*right);
auto topPositionOffset = *top;
auto leftPositionOffset = left.valueOr(-*right);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position] -> positioned inflow -> top offset(" << topPositionOffset << "px) left offset(" << leftPositionOffset << "px) layoutBox(" << &layoutBox << ")");
return { leftPositionOffset, topPositionOffset };
}
Edges FormattingContext::Geometry::computedBorder(const LayoutState&, const Box& layoutBox)
{
auto& style = layoutBox.style();
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Border] -> layoutBox: " << &layoutBox);
return {
{ style.borderLeft().boxModelWidth(), style.borderRight().boxModelWidth() },
{ style.borderTop().boxModelWidth(), style.borderBottom().boxModelWidth() }
};
}
Optional<Edges> FormattingContext::Geometry::computedPadding(const LayoutState& layoutState, const Box& layoutBox)
{
if (!layoutBox.isPaddingApplicable())
return WTF::nullopt;
auto& style = layoutBox.style();
auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Padding] -> layoutBox: " << &layoutBox);
return Edges {
{ valueForLength(style.paddingLeft(), containingBlockWidth), valueForLength(style.paddingRight(), containingBlockWidth) },
{ valueForLength(style.paddingTop(), containingBlockWidth), valueForLength(style.paddingBottom(), containingBlockWidth) }
};
}
ComputedHorizontalMargin FormattingContext::Geometry::computedHorizontalMargin(const LayoutState& layoutState, const Box& layoutBox)
{
auto& style = layoutBox.style();
auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
return { computedValueIfNotAuto(style.marginStart(), containingBlockWidth), computedValueIfNotAuto(style.marginEnd(), containingBlockWidth) };
}
ComputedVerticalMargin FormattingContext::Geometry::computedVerticalMargin(const LayoutState& layoutState, const Box& layoutBox)
{
auto& style = layoutBox.style();
auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
return { computedValueIfNotAuto(style.marginBefore(), containingBlockWidth), computedValueIfNotAuto(style.marginAfter(), containingBlockWidth) };
}
}
}
#endif