[LFC] Remove redundant InlineFormattingContext::computeBorderAndPadding
[WebKit-https.git] / Source / WebCore / layout / inlineformatting / InlineFormattingContext.cpp
index dbe7bdf..fb982b3 100644 (file)
 
 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
 
-#include "FloatingState.h"
 #include "InlineFormattingState.h"
+#include "InlineLineBreaker.h"
+#include "InlineRunProvider.h"
 #include "LayoutBox.h"
 #include "LayoutContainer.h"
-#include "LayoutContext.h"
 #include "LayoutInlineBox.h"
 #include "LayoutInlineContainer.h"
+#include "LayoutState.h"
 #include "Logging.h"
-#include "SimpleLineBreaker.h"
-#include "TextContentProvider.h"
+#include "Textutil.h"
 #include <wtf/IsoMallocInlines.h>
 #include <wtf/text/TextStream.h>
 
@@ -46,88 +46,336 @@ namespace Layout {
 
 WTF_MAKE_ISO_ALLOCATED_IMPL(InlineFormattingContext);
 
-InlineFormattingContext::InlineFormattingContext(const Box& formattingContextRoot)
-    : FormattingContext(formattingContextRoot)
+InlineFormattingContext::InlineFormattingContext(const Box& formattingContextRoot, InlineFormattingState& formattingState)
+    : FormattingContext(formattingContextRoot, formattingState)
 {
 }
 
-void InlineFormattingContext::layout(LayoutContext& layoutContext, FormattingState&) const
+static inline const Box* nextInPreOrder(const Box& layoutBox, const Container& root)
+{
+    const Box* nextInPreOrder = nullptr;
+    if (!layoutBox.establishesFormattingContext() && is<Container>(layoutBox) && downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
+        return downcast<Container>(layoutBox).firstInFlowOrFloatingChild();
+
+    for (nextInPreOrder = &layoutBox; nextInPreOrder && nextInPreOrder != &root; nextInPreOrder = nextInPreOrder->parent()) {
+        if (auto* nextSibling = nextInPreOrder->nextInFlowOrFloatingSibling())
+            return nextSibling;
+    }
+    return nullptr;
+}
+
+void InlineFormattingContext::layout() const
 {
     if (!is<Container>(root()))
         return;
 
-    LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> inline formatting context -> layout context(" << &layoutContext << ") formatting root(" << &root() << ")");
+    LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> inline formatting context -> formatting root(" << &root() << ")");
+    auto& root = downcast<Container>(this->root());
+    auto usedValues = UsedHorizontalValues { layoutState().displayBoxForLayoutBox(root).contentBoxWidth() };
+    auto* layoutBox = root.firstInFlowOrFloatingChild();
+    // Compute width/height for non-text content and margin/border/padding for inline containers.
+    while (layoutBox) {
+        if (layoutBox->establishesFormattingContext())
+            layoutFormattingContextRoot(*layoutBox, usedValues);
+        else if (is<Container>(*layoutBox)) {
+            auto& inlineContainer = downcast<InlineContainer>(*layoutBox);
+            computeMargin(inlineContainer, usedValues);
+            computeBorderAndPadding(inlineContainer, usedValues);
+        } else if (layoutBox->isReplaced())
+            computeWidthAndHeightForReplacedInlineBox(*layoutBox, usedValues);
+        layoutBox = nextInPreOrder(*layoutBox, root);
+    }
+
+    InlineRunProvider inlineRunProvider;
+    collectInlineContent(inlineRunProvider);
+    LineLayout(*this).layout(inlineRunProvider);
+    LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> inline formatting context -> formatting root(" << &root << ")");
+}
+
+void InlineFormattingContext::computeIntrinsicWidthConstraints() const
+{
+    ASSERT(is<Container>(root()));
+
+    auto& layoutState = this->layoutState();
+    auto& root = downcast<Container>(this->root());
+    ASSERT(!layoutState.formattingStateForBox(root).intrinsicWidthConstraints(root));
 
-    TextContentProvider textContentProvider;
-    auto& formattingRoot = downcast<Container>(root());
-    auto* layoutBox = formattingRoot.firstInFlowOrFloatingChild();
-    // Casually walk through the block's descendants and place the inline boxes one after the other as much as we can (yeah, I am looking at you floats).
+    Vector<const Box*> formattingContextRootList;
+    auto usedValues = UsedHorizontalValues { };
+    auto* layoutBox = root.firstInFlowOrFloatingChild();
     while (layoutBox) {
-        if (is<Container>(layoutBox)) {
-            ASSERT(is<InlineContainer>(layoutBox));
-            layoutBox = downcast<Container>(*layoutBox).firstInFlowOrFloatingChild();
-            continue;
-        }
-        auto& inlineBox = downcast<InlineBox>(*layoutBox);
-        // Only text content at this point.
-        if (inlineBox.textContent())
-            textContentProvider.appendText(*inlineBox.textContent(), inlineBox.style(), true);
-
-        for (; layoutBox; layoutBox = layoutBox->containingBlock()) {
-            if (layoutBox == &formattingRoot) {
-                layoutBox = nullptr;
-                break;
-            }
-            if (auto* nextSibling = layoutBox->nextInFlowOrFloatingSibling()) {
-                layoutBox = nextSibling;
-                break;
+        if (layoutBox->establishesFormattingContext()) {
+            formattingContextRootList.append(layoutBox);
+            if (layoutBox->isFloatingPositioned())
+                computeIntrinsicWidthForFloatBox(*layoutBox);
+            else if (layoutBox->isInlineBlockBox())
+                computeIntrinsicWidthForInlineBlock(*layoutBox);
+            else
+                ASSERT_NOT_REACHED();
+        } else if (layoutBox->isReplaced() || is<Container>(*layoutBox)) {
+            computeBorderAndPadding(*layoutBox, usedValues);
+            // inline-block and replaced.
+            auto needsWidthComputation = layoutBox->isReplaced() || layoutBox->establishesFormattingContext();
+            if (needsWidthComputation)
+                computeWidthAndMargin(*layoutBox, usedValues);
+            else {
+                // Simple inline container with no intrinsic width <span>.
+                computeMargin(*layoutBox, usedValues);
             }
         }
-        ASSERT(!layoutBox || layoutBox->isDescendantOf(formattingRoot));
+        layoutBox = nextInPreOrder(*layoutBox, root);
     }
 
-    auto& formattingRootDisplayBox = *layoutContext.displayBoxForLayoutBox(formattingRoot);
-    auto lineLeft = formattingRootDisplayBox.contentBoxLeft();
-    auto lineRight = formattingRootDisplayBox.contentBoxRight();
+    InlineRunProvider inlineRunProvider;
+    collectInlineContent(inlineRunProvider);
+
+    auto maximumLineWidth = [&](auto availableWidth) {
+        LayoutUnit maxContentLogicalRight;
+        auto lineBreaker = InlineLineBreaker { layoutState, formattingState().inlineContent(), inlineRunProvider.runs() };
+        LayoutUnit lineLogicalRight;
+
+        // Switch to the min/max formatting root width values before formatting the lines.
+        for (auto* formattingRoot : formattingContextRootList) {
+            auto intrinsicWidths = layoutState.formattingStateForBox(*formattingRoot).intrinsicWidthConstraints(*formattingRoot);
+            layoutState.displayBoxForLayoutBox(*formattingRoot).setContentBoxWidth(availableWidth ? intrinsicWidths->maximum : intrinsicWidths->minimum);
+        }
+
+        while (auto run = lineBreaker.nextRun(lineLogicalRight, availableWidth, !lineLogicalRight)) {
+            if (run->position == InlineLineBreaker::Run::Position::LineBegin)
+                lineLogicalRight = 0;
+            lineLogicalRight += run->width;
+
+            maxContentLogicalRight = std::max(maxContentLogicalRight, lineLogicalRight);
+        }
+        return maxContentLogicalRight;
+    };
+
+    auto intrinsicWidthConstraints = FormattingContext::IntrinsicWidthConstraints { maximumLineWidth(0), maximumLineWidth(LayoutUnit::max()) };
+    layoutState.formattingStateForBox(root).setIntrinsicWidthConstraints(root, intrinsicWidthConstraints);
+}
+
+void InlineFormattingContext::computeIntrinsicWidthForFloatBox(const Box& layoutBox) const
+{
+    ASSERT(layoutBox.isFloatingPositioned());
+    auto& layoutState = this->layoutState();
+
+    auto usedHorizontalValues = UsedHorizontalValues { };
+    computeBorderAndPadding(layoutBox, usedHorizontalValues);
+    computeMargin(layoutBox, usedHorizontalValues);
+    layoutState.createFormattingContext(layoutBox)->computeIntrinsicWidthConstraints();
+
+    auto usedVerticalValues = UsedVerticalValues { };
+    auto heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox, usedVerticalValues, usedHorizontalValues);
+
+    auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
+    displayBox.setContentBoxHeight(heightAndMargin.height);
+    displayBox.setVerticalMargin({ heightAndMargin.nonCollapsedMargin, { } });
+}
+
+void InlineFormattingContext::computeIntrinsicWidthForInlineBlock(const Box& layoutBox) const
+{
+    ASSERT(layoutBox.isInlineBlockBox());
+
+    auto usedValues = UsedHorizontalValues { };
+    computeBorderAndPadding(layoutBox, usedValues);
+    computeMargin(layoutBox, usedValues);
+    layoutState().createFormattingContext(layoutBox)->computeIntrinsicWidthConstraints();
+}
+
+void InlineFormattingContext::computeMargin(const Box& layoutBox, UsedHorizontalValues usedValues) const
+{
+    auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutBox, usedValues);
+    auto& displayBox = layoutState().displayBoxForLayoutBox(layoutBox);
+    displayBox.setHorizontalComputedMargin(computedHorizontalMargin);
+    displayBox.setHorizontalMargin({ computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) });
+}
 
-    auto textRuns = textContentProvider.textRuns();
-    SimpleLineBreaker::LineConstraintList constraints;
-    constraints.append({ { }, lineLeft, lineRight });
-    SimpleLineBreaker simpleLineBreaker(textRuns, textContentProvider, WTFMove(constraints), formattingRoot.style());
-    auto layoutRuns = simpleLineBreaker.runs();
+void InlineFormattingContext::computeWidthAndMargin(const Box& layoutBox, UsedHorizontalValues usedValues) const
+{
+    auto& layoutState = this->layoutState();
+    WidthAndMargin widthAndMargin;
+    if (layoutBox.isFloatingPositioned())
+        widthAndMargin = Geometry::floatingWidthAndMargin(layoutState, layoutBox, usedValues);
+    else if (layoutBox.isInlineBlockBox())
+        widthAndMargin = Geometry::inlineBlockWidthAndMargin(layoutState, layoutBox, usedValues);
+    else if (layoutBox.replaced())
+        widthAndMargin = Geometry::inlineReplacedWidthAndMargin(layoutState, layoutBox, usedValues);
+    else
+        ASSERT_NOT_REACHED();
 
-    LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> inline formatting context -> layout context(" << &layoutContext << ") formatting root(" << &root() << ")");
+    auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
+    displayBox.setContentBoxWidth(widthAndMargin.width);
+    displayBox.setHorizontalMargin(widthAndMargin.usedMargin);
+    displayBox.setHorizontalComputedMargin(widthAndMargin.computedMargin);
 }
 
-std::unique_ptr<FormattingState> InlineFormattingContext::createFormattingState(Ref<FloatingState>&& floatingState, const LayoutContext& layoutContext) const
+void InlineFormattingContext::computeHeightAndMargin(const Box& layoutBox) const
 {
-    return std::make_unique<InlineFormattingState>(WTFMove(floatingState), layoutContext);
+    auto& layoutState = this->layoutState();
+
+    HeightAndMargin heightAndMargin;
+    if (layoutBox.isFloatingPositioned())
+        heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox, { }, UsedHorizontalValues { layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth() });
+    else if (layoutBox.isInlineBlockBox())
+        heightAndMargin = Geometry::inlineBlockHeightAndMargin(layoutState, layoutBox);
+    else if (layoutBox.replaced())
+        heightAndMargin = Geometry::inlineReplacedHeightAndMargin(layoutState, layoutBox, { });
+    else
+        ASSERT_NOT_REACHED();
+
+    auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
+    displayBox.setContentBoxHeight(heightAndMargin.height);
+    displayBox.setVerticalMargin({ heightAndMargin.nonCollapsedMargin, { } });
+}
+
+void InlineFormattingContext::layoutFormattingContextRoot(const Box& root, UsedHorizontalValues usedValues) const
+{
+    ASSERT(root.isFloatingPositioned() || root.isInlineBlockBox());
+    ASSERT(usedValues.containingBlockWidth);
+
+    computeBorderAndPadding(root, usedValues);
+    computeWidthAndMargin(root, usedValues);
+    // Swich over to the new formatting context (the one that the root creates).
+    auto formattingContext = layoutState().createFormattingContext(root);
+    formattingContext->layout();
+    // Come back and finalize the root's height and margin.
+    computeHeightAndMargin(root);
+    // Now that we computed the root's height, we can go back and layout the out-of-flow descedants (if any).
+    formattingContext->layoutOutOfFlowDescendants(root);
 }
 
-Ref<FloatingState> InlineFormattingContext::createOrFindFloatingState(LayoutContext& layoutContext) const
+void InlineFormattingContext::computeWidthAndHeightForReplacedInlineBox(const Box& layoutBox, UsedHorizontalValues usedValues) const
 {
-    // If the block container box that initiates this inline formatting context also establishes a block context, the floats outside of the formatting root
-    // should not interfere with the content inside.
-    // <div style="float: left"></div><div style="overflow: hidden"> <- is a non-intrusive float, because overflow: hidden triggers new block formatting context.</div>
-    if (root().establishesBlockFormattingContext())
-        return FloatingState::create();
-    // Otherwise, the formatting context inherits the floats from the parent formatting context.
-    // Find the formatting state in which this formatting root lives, not the one it creates (this) and use its floating state.
-    auto& formattingState = layoutContext.formattingStateForBox(root());
-    return formattingState.floatingState();
-}
-
-void InlineFormattingContext::computeStaticPosition(LayoutContext&, const Box&, Display::Box&) const
+    ASSERT(!layoutBox.isContainer());
+    ASSERT(!layoutBox.establishesFormattingContext());
+    ASSERT(layoutBox.replaced());
+    ASSERT(usedValues.containingBlockWidth);
+
+    computeBorderAndPadding(layoutBox, usedValues);
+    computeWidthAndMargin(layoutBox, usedValues);
+    computeHeightAndMargin(layoutBox);
+}
+
+static void addDetachingRules(InlineItem& inlineItem, Optional<LayoutUnit> nonBreakableStartWidth, Optional<LayoutUnit> nonBreakableEndWidth)
 {
+    OptionSet<InlineItem::DetachingRule> detachingRules;
+    if (nonBreakableStartWidth) {
+        detachingRules.add(InlineItem::DetachingRule::BreakAtStart);
+        inlineItem.addNonBreakableStart(*nonBreakableStartWidth);
+    }
+    if (nonBreakableEndWidth) {
+        detachingRules.add(InlineItem::DetachingRule::BreakAtEnd);
+        inlineItem.addNonBreakableEnd(*nonBreakableEndWidth);
+    }
+    inlineItem.addDetachingRule(detachingRules);
 }
 
-void InlineFormattingContext::computeInFlowPositionedPosition(LayoutContext&, const Box&, Display::Box&) const
+static InlineItem& createAndAppendInlineItem(InlineRunProvider& inlineRunProvider, InlineContent& inlineContent, const Box& layoutBox)
 {
+    ASSERT(layoutBox.isInlineLevelBox() || layoutBox.isFloatingPositioned());
+    auto inlineItem = std::make_unique<InlineItem>(layoutBox);
+    auto* inlineItemPtr = inlineItem.get();
+    inlineContent.add(WTFMove(inlineItem));
+    inlineRunProvider.append(*inlineItemPtr);
+    return *inlineItemPtr;
 }
 
-FormattingContext::InstrinsicWidthConstraints InlineFormattingContext::instrinsicWidthConstraints(LayoutContext&, const Box&) const
+void InlineFormattingContext::collectInlineContent(InlineRunProvider& inlineRunProvider) const
 {
-    return { };
+    if (!is<Container>(root()))
+        return;
+    auto& root = downcast<Container>(this->root());
+    if (!root.hasInFlowOrFloatingChild())
+        return;
+    // The logic here is very similar to BFC layout.
+    // 1. Travers down the layout tree and collect "start" unbreakable widths (margin-left, border-left, padding-left)
+    // 2. Create InlineItem per leaf inline box (text nodes, inline-blocks, floats) and set "start" unbreakable width on them. 
+    // 3. Climb back and collect "end" unbreakable width and set it on the last InlineItem.
+    auto& layoutState = this->layoutState();
+    auto& inlineContent = formattingState().inlineContent();
+
+    enum class NonBreakableWidthType { Start, End };
+    auto nonBreakableWidth = [&](auto& container, auto type) {
+        auto& displayBox = layoutState.displayBoxForLayoutBox(container);
+        if (type == NonBreakableWidthType::Start)
+            return displayBox.marginStart() + displayBox.borderLeft() + displayBox.paddingLeft().valueOr(0);
+        return displayBox.marginEnd() + displayBox.borderRight() + displayBox.paddingRight().valueOr(0);
+    };
+
+    LayoutQueue layoutQueue;
+    layoutQueue.append(root.firstInFlowOrFloatingChild());
+
+    Optional<LayoutUnit> nonBreakableStartWidth;
+    Optional<LayoutUnit> nonBreakableEndWidth;
+    InlineItem* lastInlineItem = nullptr;
+    while (!layoutQueue.isEmpty()) {
+        while (true) {
+            auto& layoutBox = *layoutQueue.last();
+            if (!is<Container>(layoutBox))
+                break;
+            auto& container = downcast<Container>(layoutBox);
+
+            if (container.establishesFormattingContext()) {
+                // Formatting contexts are treated as leaf nodes.
+                auto& inlineItem = createAndAppendInlineItem(inlineRunProvider, inlineContent, container);
+                auto& displayBox = layoutState.displayBoxForLayoutBox(container);
+                auto currentNonBreakableStartWidth = nonBreakableStartWidth.valueOr(0) + displayBox.marginStart() + nonBreakableEndWidth.valueOr(0);
+                addDetachingRules(inlineItem, currentNonBreakableStartWidth, displayBox.marginEnd());
+                nonBreakableStartWidth = { };
+                nonBreakableEndWidth = { };
+
+                // Formatting context roots take care of their subtrees. Continue with next sibling if exists.
+                layoutQueue.removeLast();
+                if (!container.nextInFlowOrFloatingSibling())
+                    break;
+                layoutQueue.append(container.nextInFlowOrFloatingSibling());
+                continue;
+            }
+
+            // Check if this non-formatting context container has any non-breakable start properties (margin-left, border-left, padding-left)
+            // <span style="padding-left: 5px"><span style="padding-left: 5px">foobar</span></span> -> 5px + 5px
+            auto currentNonBreakableStartWidth = nonBreakableWidth(layoutBox, NonBreakableWidthType::Start);
+            if (currentNonBreakableStartWidth || layoutBox.isPositioned())
+                nonBreakableStartWidth = nonBreakableStartWidth.valueOr(0) + currentNonBreakableStartWidth;
+
+            if (!container.hasInFlowOrFloatingChild())
+                break;
+            layoutQueue.append(container.firstInFlowOrFloatingChild());
+        }
+
+        while (!layoutQueue.isEmpty()) {
+            auto& layoutBox = *layoutQueue.takeLast();
+            if (is<Container>(layoutBox)) {
+                // This is the end of an inline container. Compute the non-breakable end width and add it to the last inline box.
+                // <span style="padding-right: 5px">foobar</span> -> 5px; last inline item -> "foobar"
+                auto currentNonBreakableEndWidth = nonBreakableWidth(layoutBox, NonBreakableWidthType::End);
+                if (currentNonBreakableEndWidth || layoutBox.isPositioned())
+                    nonBreakableEndWidth = nonBreakableEndWidth.valueOr(0) + currentNonBreakableEndWidth;
+                // Add it to the last inline box
+                if (lastInlineItem) {
+                    addDetachingRules(*lastInlineItem, { }, nonBreakableEndWidth);
+                    nonBreakableEndWidth = { };
+                }
+            } else {
+                // Leaf inline box
+                auto& inlineItem = createAndAppendInlineItem(inlineRunProvider, inlineContent, layoutBox);
+                // Add start and the (through empty containers) accumulated end width.
+                // <span style="padding-left: 1px">foobar</span> -> nonBreakableStartWidth: 1px;
+                // <span style="padding: 5px"></span>foobar -> nonBreakableStartWidth: 5px; nonBreakableEndWidth: 5px
+                if (nonBreakableStartWidth || nonBreakableEndWidth) {
+                    addDetachingRules(inlineItem, nonBreakableStartWidth.valueOr(0) + nonBreakableEndWidth.valueOr(0), { });
+                    nonBreakableStartWidth = { };
+                    nonBreakableEndWidth = { };
+                }
+                lastInlineItem = &inlineItem;
+            }
+
+            if (auto* nextSibling = layoutBox.nextInFlowOrFloatingSibling()) {
+                layoutQueue.append(nextSibling);
+                break;
+            }
+        }
+    }
 }
 
 }