InlineFormattingContext.cpp [plain text]
#include "config.h"
#include "InlineFormattingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "InlineFormattingState.h"
#include "InlineTextItem.h"
#include "InvalidationState.h"
#include "LayoutBox.h"
#include "LayoutContainer.h"
#include "LayoutContext.h"
#include "LayoutState.h"
#include "Logging.h"
#include "RuntimeEnabledFeatures.h"
#include "TextUtil.h"
#include <wtf/IsoMallocInlines.h>
#include <wtf/text/TextStream.h>
namespace WebCore {
namespace Layout {
WTF_MAKE_ISO_ALLOCATED_IMPL(InlineFormattingContext);
InlineFormattingContext::InlineFormattingContext(const Container& formattingContextRoot, InlineFormattingState& formattingState)
: FormattingContext(formattingContextRoot, formattingState)
{
}
static inline const Box* nextInPreOrder(const Box& layoutBox, const Container& stayWithin)
{
const Box* nextInPreOrder = nullptr;
if (!layoutBox.establishesFormattingContext() && is<Container>(layoutBox) && downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
return downcast<Container>(layoutBox).firstInFlowOrFloatingChild();
for (nextInPreOrder = &layoutBox; nextInPreOrder && nextInPreOrder != &stayWithin; nextInPreOrder = nextInPreOrder->parent()) {
if (auto* nextSibling = nextInPreOrder->nextInFlowOrFloatingSibling())
return nextSibling;
}
return nullptr;
}
void InlineFormattingContext::layoutInFlowContent(InvalidationState& invalidationState)
{
if (!root().hasInFlowOrFloatingChild())
return;
invalidateFormattingState(invalidationState);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> inline formatting context -> formatting root(" << &root() << ")");
auto& rootGeometry = geometryForBox(root());
auto usedHorizontalValues = UsedHorizontalValues { UsedHorizontalValues::Constraints { rootGeometry } };
auto usedVerticalValues = UsedVerticalValues { UsedVerticalValues::Constraints { rootGeometry } };
auto* layoutBox = root().firstInFlowOrFloatingChild();
while (layoutBox) {
if (layoutBox->establishesFormattingContext())
layoutFormattingContextRoot(*layoutBox, invalidationState, usedHorizontalValues, usedVerticalValues);
else
computeHorizontalAndVerticalGeometry(*layoutBox, usedHorizontalValues, usedVerticalValues);
layoutBox = nextInPreOrder(*layoutBox, root());
}
collectInlineContentIfNeeded();
lineLayout(usedHorizontalValues);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> inline formatting context -> formatting root(" << &root() << ")");
}
void InlineFormattingContext::lineLayout(const UsedHorizontalValues& usedHorizontalValues)
{
auto& inlineItems = formattingState().inlineItems();
auto lineLogicalTop = geometryForBox(root()).contentBoxTop();
unsigned leadingInlineItemIndex = 0;
Optional<unsigned> partialLeadingContentLength;
auto lineBuilder = LineBuilder { *this, root().style().textAlign(), LineBuilder::IntrinsicSizing::No };
auto lineLayoutContext = LineLayoutContext { *this, root(), inlineItems };
while (leadingInlineItemIndex < inlineItems.size()) {
lineBuilder.initialize(constraintsForLine(usedHorizontalValues, lineLogicalTop));
auto lineContent = lineLayoutContext.layoutLine(lineBuilder, leadingInlineItemIndex, partialLeadingContentLength);
setDisplayBoxesForLine(lineContent, usedHorizontalValues);
partialLeadingContentLength = { };
if (lineContent.trailingInlineItemIndex) {
lineLogicalTop = lineContent.lineBox.logicalBottom();
if (lineContent.partialContent) {
leadingInlineItemIndex = *lineContent.trailingInlineItemIndex;
partialLeadingContentLength = lineContent.partialContent->overflowContentLength;
} else
leadingInlineItemIndex = *lineContent.trailingInlineItemIndex + 1;
continue;
}
ASSERT(lineBuilder.hasIntrusiveFloat());
auto floatingContext = FloatingContext { root(), *this, formattingState().floatingState() };
auto floatConstraints = floatingContext.constraints({ lineLogicalTop });
ASSERT(floatConstraints.left || floatConstraints.right);
static auto inifitePoint = PointInContextRoot::max();
lineLogicalTop = std::min(floatConstraints.left.valueOr(inifitePoint).y, floatConstraints.right.valueOr(inifitePoint).y);
ASSERT(lineLogicalTop < inifitePoint.y);
}
}
void InlineFormattingContext::layoutFormattingContextRoot(const Box& formattingContextRoot, InvalidationState& invalidationState, const UsedHorizontalValues& usedHorizontalValues, const UsedVerticalValues& usedVerticalValues)
{
ASSERT(formattingContextRoot.isFloatingPositioned() || formattingContextRoot.isInlineBlockBox());
computeBorderAndPadding(formattingContextRoot, usedHorizontalValues);
computeWidthAndMargin(formattingContextRoot, usedHorizontalValues);
if (is<Container>(formattingContextRoot)) {
auto& rootContainer = downcast<Container>(formattingContextRoot);
auto formattingContext = LayoutContext::createFormattingContext(rootContainer, layoutState());
formattingContext->layoutInFlowContent(invalidationState);
computeHeightAndMargin(rootContainer, usedHorizontalValues, usedVerticalValues);
formattingContext->layoutOutOfFlowContent(invalidationState);
} else
computeHeightAndMargin(formattingContextRoot, usedHorizontalValues, usedVerticalValues);
}
void InlineFormattingContext::computeHorizontalAndVerticalGeometry(const Box& layoutBox, const UsedHorizontalValues& usedHorizontalValues, const UsedVerticalValues& usedVerticalValues)
{
if (is<Container>(layoutBox)) {
computeHorizontalMargin(layoutBox, usedHorizontalValues);
computeBorderAndPadding(layoutBox, usedHorizontalValues);
formattingState().displayBox(layoutBox).setVerticalMargin({ { }, { } });
return;
}
if (layoutBox.isReplaced()) {
computeBorderAndPadding(layoutBox, usedHorizontalValues);
computeWidthAndMargin(layoutBox, usedHorizontalValues);
computeHeightAndMargin(layoutBox, usedHorizontalValues, usedVerticalValues);
return;
}
ASSERT(layoutBox.isAnonymous() || layoutBox.isLineBreakBox());
auto& displayBox = formattingState().displayBox(layoutBox);
displayBox.setVerticalMargin({ { }, { } });
displayBox.setHorizontalMargin({ });
displayBox.setBorder({ { }, { } });
displayBox.setPadding({ });
}
FormattingContext::IntrinsicWidthConstraints InlineFormattingContext::computedIntrinsicWidthConstraints()
{
auto& layoutState = this->layoutState();
ASSERT(!formattingState().intrinsicWidthConstraints());
if (!root().hasInFlowOrFloatingChild()) {
auto constraints = geometry().constrainByMinMaxWidth(root(), { });
formattingState().setIntrinsicWidthConstraints(constraints);
return constraints;
}
Vector<const Box*> formattingContextRootList;
auto usedHorizontalValues = UsedHorizontalValues { UsedHorizontalValues::Constraints { 0_lu, 0_lu } };
auto* layoutBox = root().firstInFlowOrFloatingChild();
while (layoutBox) {
if (layoutBox->establishesFormattingContext()) {
formattingContextRootList.append(layoutBox);
computeIntrinsicWidthForFormattingRoot(*layoutBox, usedHorizontalValues);
} else if (layoutBox->isReplaced() || is<Container>(*layoutBox)) {
computeBorderAndPadding(*layoutBox, usedHorizontalValues);
auto needsWidthComputation = layoutBox->isReplaced();
if (needsWidthComputation)
computeWidthAndMargin(*layoutBox, usedHorizontalValues);
else {
computeHorizontalMargin(*layoutBox, usedHorizontalValues);
}
}
layoutBox = nextInPreOrder(*layoutBox, root());
}
collectInlineContentIfNeeded();
auto maximumLineWidth = [&](auto availableWidth) {
for (auto* formattingRoot : formattingContextRootList) {
auto intrinsicWidths = layoutState.formattingStateForBox(*formattingRoot).intrinsicWidthConstraintsForBox(*formattingRoot);
auto& displayBox = formattingState().displayBox(*formattingRoot);
auto contentWidth = (availableWidth ? intrinsicWidths->maximum : intrinsicWidths->minimum) - displayBox.horizontalMarginBorderAndPadding();
displayBox.setContentBoxWidth(contentWidth);
}
auto usedHorizontalValues = UsedHorizontalValues { UsedHorizontalValues::Constraints { 0_lu, toLayoutUnit(availableWidth) } };
return computedIntrinsicWidthForConstraint(usedHorizontalValues);
};
auto constraints = geometry().constrainByMinMaxWidth(root(), { toLayoutUnit(maximumLineWidth(0)), toLayoutUnit(maximumLineWidth(maxInlineLayoutUnit())) });
formattingState().setIntrinsicWidthConstraints(constraints);
return constraints;
}
InlineLayoutUnit InlineFormattingContext::computedIntrinsicWidthForConstraint(const UsedHorizontalValues& usedHorizontalValues) const
{
auto& inlineItems = formattingState().inlineItems();
InlineLayoutUnit maximumLineWidth = 0;
unsigned leadingInlineItemIndex = 0;
auto lineBuilder = LineBuilder { *this, root().style().textAlign(), LineBuilder::IntrinsicSizing::Yes };
auto lineLayoutContext = LineLayoutContext { *this, root(), inlineItems };
while (leadingInlineItemIndex < inlineItems.size()) {
lineBuilder.initialize(LineBuilder::Constraints { { }, usedHorizontalValues.constraints.width, false, { } });
auto lineContent = lineLayoutContext.layoutLine(lineBuilder, leadingInlineItemIndex, { });
leadingInlineItemIndex = *lineContent.trailingInlineItemIndex + 1;
InlineLayoutUnit floatsWidth = 0;
for (auto& floatItem : lineContent.floats)
floatsWidth += geometryForBox(floatItem->layoutBox()).marginBoxWidth();
maximumLineWidth = std::max(maximumLineWidth, floatsWidth + lineContent.lineBox.logicalWidth());
}
return maximumLineWidth;
}
void InlineFormattingContext::computeIntrinsicWidthForFormattingRoot(const Box& formattingRoot, const UsedHorizontalValues& usedHorizontalValues)
{
ASSERT(formattingRoot.establishesFormattingContext());
computeBorderAndPadding(formattingRoot, usedHorizontalValues);
computeHorizontalMargin(formattingRoot, usedHorizontalValues);
auto constraints = IntrinsicWidthConstraints { };
if (auto fixedWidth = geometry().fixedValue(formattingRoot.style().logicalWidth()))
constraints = { *fixedWidth, *fixedWidth };
else if (is<Container>(formattingRoot))
constraints = LayoutContext::createFormattingContext(downcast<Container>(formattingRoot), layoutState())->computedIntrinsicWidthConstraints();
constraints = geometry().constrainByMinMaxWidth(formattingRoot, constraints);
constraints.expand(geometryForBox(formattingRoot).horizontalMarginBorderAndPadding());
formattingState().setIntrinsicWidthConstraintsForBox(formattingRoot, constraints);
}
void InlineFormattingContext::computeHorizontalMargin(const Box& layoutBox, const UsedHorizontalValues& usedHorizontalValues)
{
auto computedHorizontalMargin = geometry().computedHorizontalMargin(layoutBox, usedHorizontalValues);
auto& displayBox = formattingState().displayBox(layoutBox);
displayBox.setHorizontalComputedMargin(computedHorizontalMargin);
displayBox.setHorizontalMargin({ computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) });
}
void InlineFormattingContext::computeWidthAndMargin(const Box& layoutBox, const UsedHorizontalValues& usedHorizontalValues)
{
ContentWidthAndMargin contentWidthAndMargin;
if (layoutBox.isFloatingPositioned())
contentWidthAndMargin = geometry().floatingWidthAndMargin(layoutBox, usedHorizontalValues);
else if (layoutBox.isInlineBlockBox())
contentWidthAndMargin = geometry().inlineBlockWidthAndMargin(layoutBox, usedHorizontalValues);
else if (layoutBox.replaced())
contentWidthAndMargin = geometry().inlineReplacedWidthAndMargin(layoutBox, usedHorizontalValues);
else
ASSERT_NOT_REACHED();
auto& displayBox = formattingState().displayBox(layoutBox);
displayBox.setContentBoxWidth(contentWidthAndMargin.contentWidth);
displayBox.setHorizontalMargin(contentWidthAndMargin.usedMargin);
displayBox.setHorizontalComputedMargin(contentWidthAndMargin.computedMargin);
}
void InlineFormattingContext::computeHeightAndMargin(const Box& layoutBox, const UsedHorizontalValues& usedHorizontalValues, const UsedVerticalValues& usedVerticalValues)
{
ContentHeightAndMargin contentHeightAndMargin;
if (layoutBox.isFloatingPositioned())
contentHeightAndMargin = geometry().floatingHeightAndMargin(layoutBox, usedHorizontalValues, usedVerticalValues);
else if (layoutBox.isInlineBlockBox())
contentHeightAndMargin = geometry().inlineBlockHeightAndMargin(layoutBox, usedHorizontalValues, usedVerticalValues);
else if (layoutBox.replaced())
contentHeightAndMargin = geometry().inlineReplacedHeightAndMargin(layoutBox, usedHorizontalValues, usedVerticalValues);
else
ASSERT_NOT_REACHED();
auto& displayBox = formattingState().displayBox(layoutBox);
displayBox.setContentBoxHeight(contentHeightAndMargin.contentHeight);
displayBox.setVerticalMargin({ contentHeightAndMargin.nonCollapsedMargin, { } });
}
void InlineFormattingContext::computeWidthAndHeightForReplacedInlineBox(const Box& layoutBox, const UsedHorizontalValues& usedHorizontalValues, const UsedVerticalValues& usedVerticalValues)
{
ASSERT(!layoutBox.isContainer());
ASSERT(!layoutBox.establishesFormattingContext());
ASSERT(layoutBox.replaced());
computeBorderAndPadding(layoutBox, usedHorizontalValues);
computeWidthAndMargin(layoutBox, usedHorizontalValues);
computeHeightAndMargin(layoutBox, usedHorizontalValues, usedVerticalValues);
}
void InlineFormattingContext::collectInlineContentIfNeeded()
{
auto& formattingState = this->formattingState();
if (!formattingState.inlineItems().isEmpty())
return;
LayoutQueue layoutQueue;
if (root().hasInFlowOrFloatingChild())
layoutQueue.append(root().firstInFlowOrFloatingChild());
while (!layoutQueue.isEmpty()) {
auto treatAsInlineContainer = [](auto& layoutBox) {
return is<Container>(layoutBox) && !layoutBox.establishesFormattingContext();
};
while (true) {
auto& layoutBox = *layoutQueue.last();
if (!treatAsInlineContainer(layoutBox))
break;
formattingState.addInlineItem(makeUnique<InlineItem>(layoutBox, InlineItem::Type::ContainerStart));
auto& container = downcast<Container>(layoutBox);
if (!container.hasInFlowOrFloatingChild())
break;
layoutQueue.append(container.firstInFlowOrFloatingChild());
}
while (!layoutQueue.isEmpty()) {
auto& layoutBox = *layoutQueue.takeLast();
if (treatAsInlineContainer(layoutBox))
formattingState.addInlineItem(makeUnique<InlineItem>(layoutBox, InlineItem::Type::ContainerEnd));
else if (layoutBox.isLineBreakBox())
formattingState.addInlineItem(makeUnique<InlineItem>(layoutBox, InlineItem::Type::HardLineBreak));
else if (layoutBox.isFloatingPositioned())
formattingState.addInlineItem(makeUnique<InlineItem>(layoutBox, InlineItem::Type::Float));
else {
ASSERT(layoutBox.isInlineLevelBox());
if (layoutBox.hasTextContent())
InlineTextItem::createAndAppendTextItems(formattingState.inlineItems(), layoutBox);
else
formattingState.addInlineItem(makeUnique<InlineItem>(layoutBox, InlineItem::Type::Box));
}
if (auto* nextSibling = layoutBox.nextInFlowOrFloatingSibling()) {
layoutQueue.append(nextSibling);
break;
}
}
}
}
LineBuilder::Constraints InlineFormattingContext::constraintsForLine(const UsedHorizontalValues& usedHorizontalValues, InlineLayoutUnit lineLogicalTop)
{
auto lineLogicalLeft = geometryForBox(root()).contentBoxLeft();
auto lineLogicalRight = lineLogicalLeft + usedHorizontalValues.constraints.width;
auto lineIsConstrainedByFloat = false;
auto floatingContext = FloatingContext { root(), *this, formattingState().floatingState() };
if (!floatingContext.isEmpty()) {
auto floatConstraints = floatingContext.constraints({ toLayoutUnit(lineLogicalTop) });
if (floatConstraints.left && floatConstraints.left->x <= lineLogicalLeft)
floatConstraints.left = { };
auto lineLogicalRight = geometryForBox(root()).contentBoxRight();
if (floatConstraints.right && floatConstraints.right->x >= lineLogicalRight)
floatConstraints.right = { };
lineIsConstrainedByFloat = floatConstraints.left || floatConstraints.right;
if (floatConstraints.left && floatConstraints.right) {
ASSERT(floatConstraints.left->x <= floatConstraints.right->x);
lineLogicalRight = floatConstraints.right->x;
lineLogicalLeft = floatConstraints.left->x;
} else if (floatConstraints.left) {
ASSERT(floatConstraints.left->x >= lineLogicalLeft);
lineLogicalLeft = floatConstraints.left->x;
} else if (floatConstraints.right) {
ASSERT(floatConstraints.right->x >= lineLogicalLeft);
lineLogicalRight = floatConstraints.right->x;
}
}
auto computedTextIndent = [&] {
auto& root = this->root();
auto isFormattingContextRootCandidateToTextIndent = !root.isAnonymous();
if (root.isAnonymous()) {
isFormattingContextRootCandidateToTextIndent = RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled() ?
layoutState().isIntegratedRootBoxFirstChild() : root.parent()->firstInFlowChild() == &root;
}
if (!isFormattingContextRootCandidateToTextIndent)
return InlineLayoutUnit { };
auto invertLineRange = false;
#if ENABLE(CSS3_TEXT)
invertLineRange = root.style().textIndentType() == TextIndentType::Hanging;
#endif
auto isFirstLine = formattingState().ensureDisplayInlineContent().lineBoxes.isEmpty();
auto shouldIndent = invertLineRange != isFirstLine;
if (!shouldIndent)
return InlineLayoutUnit { };
return geometry().computedTextIndent(root, usedHorizontalValues.constraints).valueOr(InlineLayoutUnit { });
};
lineLogicalLeft += computedTextIndent();
return LineBuilder::Constraints { { lineLogicalLeft, lineLogicalTop }, lineLogicalRight - lineLogicalLeft, lineIsConstrainedByFloat, quirks().lineHeightConstraints(root()) };
}
void InlineFormattingContext::setDisplayBoxesForLine(const LineLayoutContext::LineContent& lineContent, const UsedHorizontalValues& usedHorizontalValues)
{
auto& formattingState = this->formattingState();
if (!lineContent.floats.isEmpty()) {
auto floatingContext = FloatingContext { root(), *this, formattingState.floatingState() };
for (const auto& floatItem : lineContent.floats) {
auto& floatBox = floatItem->layoutBox();
auto& displayBox = formattingState.displayBox(floatBox);
auto& lineBox = lineContent.lineBox;
displayBox.setTopLeft({ lineBox.logicalLeft(), lineBox.logicalTop() });
displayBox.setTopLeft(floatingContext.positionForFloat(floatBox));
floatingContext.append(floatBox);
}
}
auto initialContaingBlockSize = LayoutSize { };
if (RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled()) {
initialContaingBlockSize = layoutState().viewportSize();
} else
initialContaingBlockSize = geometryForBox(root().initialContainingBlock()).contentBox().size();
auto& inlineContent = formattingState.ensureDisplayInlineContent();
auto lineIndex = inlineContent.lineBoxes.size();
inlineContent.lineBoxes.append(lineContent.lineBox);
auto lineInkOverflow = lineContent.lineBox.scrollableOverflow();
Optional<unsigned> lastTextItemIndex;
auto& lineRuns = lineContent.runList;
for (unsigned index = 0; index < lineRuns.size(); ++index) {
auto& lineRun = lineRuns.at(index);
auto& logicalRect = lineRun.logicalRect();
auto& layoutBox = lineRun.layoutBox();
auto& displayBox = formattingState.displayBox(layoutBox);
auto initiatesInlineRun = !lineRun.isContainerStart() && !lineRun.isContainerEnd() && !lineRun.isCollapsedToVisuallyEmpty();
if (initiatesInlineRun) {
auto computedInkOverflow = [&] {
if (!lineRun.isText())
return logicalRect;
auto& style = lineRun.style();
auto inkOverflow = logicalRect;
auto strokeOverflow = std::ceil(style.computedStrokeWidth(ceiledIntSize(initialContaingBlockSize)));
inkOverflow.inflate(strokeOverflow);
auto letterSpacing = style.fontCascade().letterSpacing();
if (letterSpacing < 0) {
inkOverflow.expandHorizontally(-letterSpacing);
}
return inkOverflow;
};
auto inkOverflow = computedInkOverflow();
lineInkOverflow.expandToContain(inkOverflow);
inlineContent.runs.append({ lineIndex, lineRun.layoutBox(), logicalRect, inkOverflow, lineRun.textContext() });
}
if (lineRun.isLineBreak()) {
displayBox.setTopLeft(toLayoutPoint(logicalRect.topLeft()));
displayBox.setContentBoxWidth(toLayoutUnit(logicalRect.width()));
displayBox.setContentBoxHeight(toLayoutUnit(logicalRect.height()));
continue;
}
if (lineRun.isBox()) {
auto topLeft = logicalRect.topLeft();
if (layoutBox.isInFlowPositioned())
topLeft += geometry().inFlowPositionedPositionOffset(layoutBox, usedHorizontalValues);
displayBox.setTopLeft(toLayoutPoint(topLeft));
continue;
}
if (lineRun.isContainerStart()) {
displayBox.setTopLeft(toLayoutPoint(logicalRect.topLeft()));
continue;
}
if (lineRun.isContainerEnd()) {
if (layoutBox.isInFlowPositioned()) {
auto inflowOffset = geometry().inFlowPositionedPositionOffset(layoutBox, usedHorizontalValues);
displayBox.moveHorizontally(inflowOffset.width());
displayBox.moveVertically(inflowOffset.height());
}
auto marginBoxWidth = logicalRect.left() - displayBox.left();
auto contentBoxWidth = marginBoxWidth - (displayBox.marginStart() + displayBox.borderLeft() + displayBox.paddingLeft().valueOr(0));
displayBox.setContentBoxWidth(toLayoutUnit(contentBoxWidth));
displayBox.setContentBoxHeight(toLayoutUnit(logicalRect.height()));
continue;
}
if (lineRun.isText()) {
lastTextItemIndex = inlineContent.runs.size() - 1;
auto firstRunForLayoutBox = !index || &lineRuns[index - 1].layoutBox() != &layoutBox;
if (firstRunForLayoutBox) {
displayBox.setTopLeft(toLayoutPoint(logicalRect.topLeft()));
displayBox.setContentBoxWidth(toLayoutUnit(logicalRect.width()));
displayBox.setContentBoxHeight(toLayoutUnit(logicalRect.height()));
} else {
displayBox.setContentBoxWidth(toLayoutUnit(displayBox.contentBoxWidth() + logicalRect.width()));
}
continue;
}
ASSERT_NOT_REACHED();
}
if (lineContent.partialContent && lineContent.partialContent->trailingContentNeedsHyphen)
inlineContent.runs[*lastTextItemIndex].textContext()->setNeedsHyphen();
inlineContent.lineBoxes.last().setInkOverflow(lineInkOverflow);
}
void InlineFormattingContext::invalidateFormattingState(const InvalidationState&)
{
formattingState().clearDisplayInlineContent();
}
}
}
#endif