| /* |
| * Copyright (C) 2018 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "InlineFormattingContext.h" |
| |
| #if ENABLE(LAYOUT_FORMATTING_CONTEXT) |
| |
| #include "FloatingContext.h" |
| #include "FontCascade.h" |
| #include "InlineDamage.h" |
| #include "InlineDisplayBox.h" |
| #include "InlineDisplayContentBuilder.h" |
| #include "InlineFormattingState.h" |
| #include "InlineItemsBuilder.h" |
| #include "InlineLineBox.h" |
| #include "InlineLineBoxBuilder.h" |
| #include "InlineTextItem.h" |
| #include "LayoutBox.h" |
| #include "LayoutContainerBox.h" |
| #include "LayoutContext.h" |
| #include "LayoutInitialContainingBlock.h" |
| #include "LayoutInlineTextBox.h" |
| #include "LayoutLineBreakBox.h" |
| #include "LayoutReplacedBox.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 ContainerBox& formattingContextRoot, InlineFormattingState& formattingState, const InlineDamage* lineDamage) |
| : FormattingContext(formattingContextRoot, formattingState) |
| , m_lineDamage(lineDamage) |
| , m_inlineFormattingGeometry(*this) |
| , m_inlineFormattingQuirks(*this) |
| { |
| } |
| |
| static inline const Box* nextInlineLevelBoxToLayout(const Box& layoutBox, const ContainerBox& stayWithin) |
| { |
| // Atomic inline-level boxes and floats are opaque boxes meaning that they are |
| // responsible for their own content (do not need to descend into their subtrees). |
| // Only inline boxes may have relevant descendant content. |
| if (layoutBox.isInlineBox()) { |
| if (is<ContainerBox>(layoutBox) && downcast<ContainerBox>(layoutBox).hasInFlowOrFloatingChild()) { |
| // Anonymous inline text boxes/line breaks can't have descendant content by definition. |
| ASSERT(!layoutBox.isInlineTextBox() && !layoutBox.isLineBreakBox()); |
| return downcast<ContainerBox>(layoutBox).firstInFlowOrFloatingChild(); |
| } |
| } |
| |
| for (auto* nextInPreOrder = &layoutBox; nextInPreOrder && nextInPreOrder != &stayWithin; nextInPreOrder = &nextInPreOrder->parent()) { |
| if (auto* nextSibling = nextInPreOrder->nextInFlowOrFloatingSibling()) |
| return nextSibling; |
| } |
| return nullptr; |
| } |
| |
| void InlineFormattingContext::layoutInFlowContent(const ConstraintsForInFlowContent& constraints) |
| { |
| LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> inline formatting context -> formatting root(" << &root() << ")"); |
| ASSERT(root().hasInFlowOrFloatingChild()); |
| |
| invalidateFormattingState(); |
| auto* layoutBox = root().firstInFlowOrFloatingChild(); |
| // 1. Visit each inline box and partially compute their geometry (margins, padding and borders). |
| // 2. Collect the inline items (flatten the the layout tree) and place them on lines in bidirectional order. |
| while (layoutBox) { |
| ASSERT(layoutBox->isInlineLevelBox() || layoutBox->isFloatingPositioned()); |
| |
| if (layoutBox->isAtomicInlineLevelBox() || layoutBox->isFloatingPositioned()) { |
| // Inline-blocks, inline-tables and replaced elements (img, video) can be sized but not yet positioned. |
| if (is<ContainerBox>(layoutBox) && layoutBox->establishesFormattingContext()) { |
| ASSERT(layoutBox->isInlineBlockBox() || layoutBox->isInlineTableBox() || layoutBox->isFloatingPositioned()); |
| auto& formattingRoot = downcast<ContainerBox>(*layoutBox); |
| computeBorderAndPadding(formattingRoot, constraints.horizontal()); |
| computeWidthAndMargin(formattingRoot, constraints.horizontal()); |
| |
| if (formattingRoot.hasChild()) { |
| auto formattingContext = LayoutContext::createFormattingContext(formattingRoot, layoutState()); |
| if (formattingRoot.hasInFlowOrFloatingChild()) |
| formattingContext->layoutInFlowContent(formattingGeometry().constraintsForInFlowContent(formattingRoot)); |
| computeHeightAndMargin(formattingRoot, constraints.horizontal()); |
| formattingContext->layoutOutOfFlowContent(formattingGeometry().constraintsForOutOfFlowContent(formattingRoot)); |
| } else |
| computeHeightAndMargin(formattingRoot, constraints.horizontal()); |
| } else { |
| // Replaced and other type of leaf atomic inline boxes. |
| computeBorderAndPadding(*layoutBox, constraints.horizontal()); |
| computeWidthAndMargin(*layoutBox, constraints.horizontal()); |
| computeHeightAndMargin(*layoutBox, constraints.horizontal()); |
| } |
| } else if (layoutBox->isLineBreakBox()) { |
| auto& boxGeometry = formattingState().boxGeometry(*layoutBox); |
| boxGeometry.setHorizontalMargin({ }); |
| boxGeometry.setBorder({ }); |
| boxGeometry.setPadding({ }); |
| boxGeometry.setContentBoxWidth({ }); |
| boxGeometry.setVerticalMargin({ }); |
| } else if (layoutBox->isInlineBox()) { |
| // Text wrapper boxes (anonymous inline level boxes) don't have box geometries (they only generate boxes). |
| if (!layoutBox->isInlineTextBox()) { |
| // Inline boxes (<span>) can't get sized/positioned yet. At this point we can only compute their margins, borders and padding. |
| computeBorderAndPadding(*layoutBox, constraints.horizontal()); |
| computeHorizontalMargin(*layoutBox, constraints.horizontal()); |
| formattingState().boxGeometry(*layoutBox).setVerticalMargin({ }); |
| } |
| } else |
| ASSERT_NOT_REACHED(); |
| |
| layoutBox = nextInlineLevelBoxToLayout(*layoutBox, root()); |
| } |
| |
| collectContentIfNeeded(); |
| |
| auto& inlineItems = formattingState().inlineItems(); |
| lineLayout(inlineItems, { 0, inlineItems.size() }, constraints); |
| computeStaticPositionForOutOfFlowContent(formattingState().outOfFlowBoxes()); |
| LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> inline formatting context -> formatting root(" << &root() << ")"); |
| } |
| |
| void InlineFormattingContext::lineLayoutForIntergration(const ConstraintsForInFlowContent& constraints) |
| { |
| invalidateFormattingState(); |
| collectContentIfNeeded(); |
| auto& inlineItems = formattingState().inlineItems(); |
| lineLayout(inlineItems, { 0, inlineItems.size() }, constraints); |
| computeStaticPositionForOutOfFlowContent(formattingState().outOfFlowBoxes()); |
| } |
| |
| IntrinsicWidthConstraints InlineFormattingContext::computedIntrinsicWidthConstraintsForIntegration() |
| { |
| if (formattingState().intrinsicWidthConstraints()) |
| return *formattingState().intrinsicWidthConstraints(); |
| |
| collectContentIfNeeded(); |
| |
| auto constraints = formattingGeometry().constrainByMinMaxWidth(root(), |
| { ceiledLayoutUnit(computedIntrinsicWidthForConstraint(IntrinsicWidthMode::Minimum)) |
| , ceiledLayoutUnit(computedIntrinsicWidthForConstraint(IntrinsicWidthMode::Maximum)) }); |
| formattingState().setIntrinsicWidthConstraints(constraints); |
| return constraints; |
| } |
| |
| LayoutUnit InlineFormattingContext::usedContentHeight() const |
| { |
| // 10.6.7 'Auto' heights for block formatting context roots |
| |
| // If it only has inline-level children, the height is the distance between the top of the topmost line box and the bottom of the bottommost line box. |
| |
| // In addition, if the element has any floating descendants whose bottom margin edge is below the element's bottom content edge, |
| // then the height is increased to include those edges. Only floats that participate in this block formatting context are taken |
| // into account, e.g., floats inside absolutely positioned descendants or other floats are not. |
| auto& lines = formattingState().lines(); |
| // Even empty content generates a line. |
| ASSERT(!lines.isEmpty()); |
| auto top = LayoutUnit { lines.first().lineBoxLogicalRect().top() }; |
| auto bottom = LayoutUnit { lines.last().lineBoxLogicalRect().bottom() + formattingState().clearGapAfterLastLine() }; |
| |
| auto floatingContext = FloatingContext { *this, formattingState().floatingState() }; |
| if (auto floatBottom = floatingContext.bottom()) { |
| bottom = std::max(*floatBottom, bottom); |
| top = std::min(*floatingContext.top(), top); |
| } |
| return bottom - top; |
| } |
| |
| void InlineFormattingContext::lineLayout(InlineItems& inlineItems, LineBuilder::InlineItemRange needsLayoutRange, const ConstraintsForInFlowContent& constraints) |
| { |
| auto& formattingState = this->formattingState(); |
| formattingState.boxes().reserveInitialCapacity(formattingState.inlineItems().size()); |
| InlineLayoutUnit lineLogicalTop = constraints.logicalTop(); |
| struct PreviousLine { |
| LineBuilder::InlineItemRange range; |
| size_t overflowContentLength { 0 }; |
| std::optional<InlineLayoutUnit> overflowLogicalWidth; |
| }; |
| std::optional<PreviousLine> previousLine; |
| auto& floatingState = formattingState.floatingState(); |
| auto floatingContext = FloatingContext { *this, floatingState }; |
| auto isFirstLine = formattingState.lines().isEmpty(); |
| |
| auto lineBuilder = LineBuilder { *this, floatingState, constraints.horizontal(), inlineItems }; |
| while (!needsLayoutRange.isEmpty()) { |
| // Turn previous line's overflow content length into the next line's leading content partial length. |
| // "sp[<-line break->]lit_content" -> overflow length: 11 -> leading partial content length: 11. |
| auto partialLeadingContentLength = previousLine ? previousLine->overflowContentLength : 0; |
| auto leadingLogicalWidth = previousLine ? previousLine->overflowLogicalWidth : std::nullopt; |
| auto initialLineHeight = [&]() -> InlineLayoutUnit { |
| if (layoutState().inStandardsMode()) |
| return root().style().computedLineHeight(); |
| return formattingQuirks().initialLineHeight(); |
| }(); |
| auto initialLineConstraints = InlineRect { lineLogicalTop, constraints.horizontal().logicalLeft, constraints.horizontal().logicalWidth, initialLineHeight }; |
| auto lineContent = lineBuilder.layoutInlineContent(needsLayoutRange, partialLeadingContentLength, leadingLogicalWidth, initialLineConstraints, isFirstLine); |
| auto lineLogicalRect = computeGeometryForLineContent(lineContent); |
| |
| auto lineContentRange = lineContent.inlineItemRange; |
| if (!lineContentRange.isEmpty()) { |
| ASSERT(needsLayoutRange.start < lineContentRange.end); |
| isFirstLine = false; |
| lineLogicalTop = formattingGeometry().logicalTopForNextLine(lineContent, lineLogicalRect.bottom(), floatingContext); |
| if (lineContent.isLastLineWithInlineContent) { |
| // The final content height of this inline formatting context should include the cleared floats as well. |
| formattingState.setClearGapAfterLastLine(lineLogicalTop - lineLogicalRect.bottom()); |
| } |
| // When the trailing content is partial, we need to reuse the last InlineTextItem. |
| auto lastInlineItemNeedsPartialLayout = lineContent.partialTrailingContentLength; |
| if (lastInlineItemNeedsPartialLayout) { |
| auto lineLayoutHasAdvanced = !previousLine |
| || lineContentRange.end > previousLine->range.end |
| || (previousLine->overflowContentLength && previousLine->overflowContentLength > lineContent.partialTrailingContentLength); |
| if (!lineLayoutHasAdvanced) { |
| ASSERT_NOT_REACHED(); |
| // Move over to the next run if we are stuck on this partial content (when the overflow content length remains the same). |
| // We certainly lose some content, but we would be busy looping otherwise. |
| lastInlineItemNeedsPartialLayout = false; |
| } |
| } |
| needsLayoutRange.start = lastInlineItemNeedsPartialLayout ? lineContentRange.end - 1 : lineContentRange.end; |
| previousLine = PreviousLine { lineContentRange, lineContent.partialTrailingContentLength, lineContent.overflowLogicalWidth }; |
| continue; |
| } |
| // Floats prevented us placing any content on the line. |
| ASSERT(lineContent.runs.isEmpty()); |
| ASSERT(lineContent.hasIntrusiveFloat); |
| // Move the next line below the intrusive float(s). |
| auto logicalTopCandidateForNextLine = [&] { |
| auto lineBottomWithNoInlineContent = LayoutUnit { std::max(lineLogicalRect.bottom(), initialLineConstraints.bottom()) }; |
| auto floatConstraints = floatingContext.constraints(toLayoutUnit(lineLogicalTop), lineBottomWithNoInlineContent); |
| ASSERT(floatConstraints.left || floatConstraints.right); |
| if (floatConstraints.left && floatConstraints.right) { |
| // In case of left and right constraints, we need to pick the one that's closer to the current line. |
| return std::min(floatConstraints.left->y, floatConstraints.right->y); |
| } |
| if (floatConstraints.left) |
| return floatConstraints.left->y; |
| if (floatConstraints.right) |
| return floatConstraints.right->y; |
| ASSERT_NOT_REACHED(); |
| return lineBottomWithNoInlineContent; |
| }; |
| lineLogicalTop = logicalTopCandidateForNextLine(); |
| } |
| } |
| |
| void InlineFormattingContext::computeStaticPositionForOutOfFlowContent(const FormattingState::OutOfFlowBoxList& outOfFlowBoxes) |
| { |
| // This function computes the static position for out-of-flow content inside the inline formatting context. |
| // As per spec, the static position of an out-of-flow box is computed as if the position was set to static. |
| // However it does not mean that the out-of-flow box should be involved in the inline layout process. |
| // Instead we figure out this static position after the inline layout by looking at the previous/next sibling (or parent) box's geometry and |
| // place the out-of-flow box at the logical right position. |
| auto& formattingState = this->formattingState(); |
| auto& lines = formattingState.lines(); |
| auto& boxes = formattingState.boxes(); |
| |
| for (auto& outOfFlowBox : outOfFlowBoxes) { |
| auto& outOfFlowGeometry = formattingState.boxGeometry(outOfFlowBox); |
| // Both previous float and out-of-flow boxes are skipped here. A series of adjoining out-of-flow boxes should all be placed |
| // at the same static position (they don't affect next-sibling positions) and while floats do participate in the inline layout |
| // their positions have already been taken into account during the inline layout. |
| auto previousContentSkippingFloats = [&]() -> const Layout::Box* { |
| auto* previousSibling = outOfFlowBox->previousSibling(); |
| for (; previousSibling && previousSibling->isFloatingPositioned(); previousSibling = previousSibling->previousSibling()) { } |
| if (previousSibling) |
| return previousSibling; |
| // Parent is either the root here or another inline box (e.g. <span><img style="position: absolute"></span>) |
| auto& parent = outOfFlowBox->parent(); |
| return &parent == &root() ? nullptr : &parent; |
| }(); |
| |
| if (!previousContentSkippingFloats) { |
| // This is the first (non-float)child. Let's place it to the left of the first box. |
| // <div><img style="position: absolute">text content</div> |
| ASSERT(boxes.size()); |
| outOfFlowGeometry.setLogicalTopLeft({ boxes[0].logicalLeft(), lines[0].lineBoxLogicalRect().top() }); |
| continue; |
| } |
| |
| if (previousContentSkippingFloats->isOutOfFlowPositioned()) { |
| // Subsequent out-of-flow positioned boxes share the same static position. |
| // <div>text content<img style="position: absolute"><img style="position: absolute"></div> |
| outOfFlowGeometry.setLogicalTopLeft(BoxGeometry::borderBoxTopLeft(geometryForBox(*previousContentSkippingFloats))); |
| continue; |
| } |
| |
| ASSERT(previousContentSkippingFloats->isInFlow()); |
| auto placeOutOfFlowBoxAfterPreviousInFlowBox = [&] { |
| // The out-of-flow box should be placed after this inflow box. |
| // Skip to the last box of this layout box. The last box's geometry is used to compute the out-of-flow box's static position. |
| size_t lastBoxIndexOnPreviousLayoutBox = 0; |
| for (; lastBoxIndexOnPreviousLayoutBox < boxes.size() && &boxes[lastBoxIndexOnPreviousLayoutBox].layoutBox() != previousContentSkippingFloats; ++lastBoxIndexOnPreviousLayoutBox) { } |
| if (lastBoxIndexOnPreviousLayoutBox == boxes.size()) { |
| // FIXME: In very rare cases, the previous box's content might have been completely collapsed and left us with no box. |
| ASSERT_NOT_IMPLEMENTED_YET(); |
| return; |
| } |
| for (; lastBoxIndexOnPreviousLayoutBox < boxes.size() && &boxes[lastBoxIndexOnPreviousLayoutBox].layoutBox() == previousContentSkippingFloats; ++lastBoxIndexOnPreviousLayoutBox) { } |
| --lastBoxIndexOnPreviousLayoutBox; |
| // Let's check if the previous box is the last box on the current line and use the next box's left instead. |
| auto& previousBox = boxes[lastBoxIndexOnPreviousLayoutBox]; |
| auto* nextBox = lastBoxIndexOnPreviousLayoutBox + 1 < boxes.size() ? &boxes[lastBoxIndexOnPreviousLayoutBox + 1] : nullptr; |
| |
| if (nextBox && nextBox->lineIndex() == previousBox.lineIndex()) { |
| // Previous and next boxes are on the same line. The out-of-flow box is right at the previous box's logical right. |
| // <div>text<img style="position: absolute">content</div> |
| auto logicalLeft = previousBox.logicalRight(); |
| if (previousContentSkippingFloats->isInlineBox() && !previousContentSkippingFloats->isAnonymous()) { |
| // <div>text<span><img style="position: absolute">content</span></div> |
| // or |
| // <div>text<span>content</span><img style="position: absolute"></div> |
| auto& inlineBoxBoxGeometry = geometryForBox(*previousContentSkippingFloats); |
| logicalLeft = previousContentSkippingFloats == &outOfFlowBox->parent() |
| ? BoxGeometry::borderBoxLeft(inlineBoxBoxGeometry) + inlineBoxBoxGeometry.contentBoxLeft() |
| : BoxGeometry::borderBoxRect(inlineBoxBoxGeometry).right(); |
| } |
| outOfFlowGeometry.setLogicalTopLeft({ logicalLeft, lines[previousBox.lineIndex()].lineBoxLogicalRect().top() }); |
| return; |
| } |
| |
| if (nextBox) { |
| // The out of flow box is placed at the beginning of the next line (where the first box on the line is). |
| // <div>text<br><img style="position: absolute"><img style="position: absolute">content</div> |
| outOfFlowGeometry.setLogicalTopLeft({ nextBox->logicalLeft(), lines[nextBox->lineIndex()].lineBoxLogicalRect().top() }); |
| return; |
| } |
| |
| auto& lastLineLogicalRect = lines[previousBox.lineIndex()].lineBoxLogicalRect(); |
| // This out-of-flow box is the last box. |
| // FIXME: Use isLineBreak instead to cover preserved new lines too. |
| if (previousBox.layoutBox().isLineBreakBox()) { |
| // <div>text<br><img style="position: absolute"><img style="position: absolute"></div> |
| outOfFlowGeometry.setLogicalTopLeft({ lastLineLogicalRect.left(), lastLineLogicalRect.bottom() }); |
| return; |
| } |
| // FIXME: We may need to check if this box actually fits the last line and move it over to the "next" line. |
| outOfFlowGeometry.setLogicalTopLeft({ previousBox.logicalRight(), lastLineLogicalRect.top() }); |
| }; |
| placeOutOfFlowBoxAfterPreviousInFlowBox(); |
| } |
| } |
| |
| IntrinsicWidthConstraints InlineFormattingContext::computedIntrinsicWidthConstraints() |
| { |
| auto& layoutState = this->layoutState(); |
| if (formattingState().intrinsicWidthConstraints()) |
| return *formattingState().intrinsicWidthConstraints(); |
| |
| if (!root().hasInFlowOrFloatingChild()) { |
| auto constraints = formattingGeometry().constrainByMinMaxWidth(root(), { }); |
| formattingState().setIntrinsicWidthConstraints(constraints); |
| return constraints; |
| } |
| |
| Vector<const Box*> formattingContextRootList; |
| auto horizontalConstraints = HorizontalConstraints { 0_lu, 0_lu }; |
| auto* layoutBox = root().firstInFlowOrFloatingChild(); |
| // In order to compute the max/min widths, we need to compute margins, borders and padding for certain inline boxes first. |
| while (layoutBox) { |
| if (layoutBox->isInlineTextBox()) { |
| layoutBox = nextInlineLevelBoxToLayout(*layoutBox, root()); |
| continue; |
| } |
| if (layoutBox->isReplacedBox()) { |
| computeBorderAndPadding(*layoutBox, horizontalConstraints); |
| computeWidthAndMargin(*layoutBox, horizontalConstraints); |
| } else if (layoutBox->isFloatingPositioned() || layoutBox->isAtomicInlineLevelBox()) { |
| ASSERT(layoutBox->establishesFormattingContext()); |
| formattingContextRootList.append(layoutBox); |
| |
| computeBorderAndPadding(*layoutBox, horizontalConstraints); |
| computeHorizontalMargin(*layoutBox, horizontalConstraints); |
| computeIntrinsicWidthForFormattingRoot(*layoutBox); |
| } else if (layoutBox->isInlineBox()) { |
| computeBorderAndPadding(*layoutBox, horizontalConstraints); |
| computeHorizontalMargin(*layoutBox, horizontalConstraints); |
| } else |
| ASSERT_NOT_REACHED(); |
| layoutBox = nextInlineLevelBoxToLayout(*layoutBox, root()); |
| } |
| |
| collectContentIfNeeded(); |
| |
| auto maximumLineWidth = [&](auto intrinsicWidthMode) { |
| // Switch to the min/max formatting root width values before formatting the lines. |
| for (auto* formattingRoot : formattingContextRootList) { |
| auto intrinsicWidths = layoutState.formattingStateForBox(*formattingRoot).intrinsicWidthConstraintsForBox(*formattingRoot); |
| auto& boxGeometry = formattingState().boxGeometry(*formattingRoot); |
| auto contentWidth = (intrinsicWidthMode == IntrinsicWidthMode::Maximum ? intrinsicWidths->maximum : intrinsicWidths->minimum) - boxGeometry.horizontalMarginBorderAndPadding(); |
| boxGeometry.setContentBoxWidth(contentWidth); |
| } |
| return computedIntrinsicWidthForConstraint(intrinsicWidthMode); |
| }; |
| |
| auto minimumContentWidth = ceiledLayoutUnit(maximumLineWidth(IntrinsicWidthMode::Minimum)); |
| auto maximumContentWidth = ceiledLayoutUnit(maximumLineWidth(IntrinsicWidthMode::Maximum)); |
| auto constraints = formattingGeometry().constrainByMinMaxWidth(root(), { minimumContentWidth, maximumContentWidth }); |
| formattingState().setIntrinsicWidthConstraints(constraints); |
| return constraints; |
| } |
| |
| InlineLayoutUnit InlineFormattingContext::computedIntrinsicWidthForConstraint(IntrinsicWidthMode intrinsicWidthMode) const |
| { |
| auto& inlineItems = formattingState().inlineItems(); |
| auto lineBuilder = LineBuilder { *this, inlineItems, intrinsicWidthMode }; |
| auto layoutRange = LineBuilder::InlineItemRange { 0 , inlineItems.size() }; |
| auto maximumLineWidth = InlineLayoutUnit { }; |
| auto maximumFloatWidth = LayoutUnit { }; |
| auto isFirstLine = true; |
| while (!layoutRange.isEmpty()) { |
| auto intrinsicContent = lineBuilder.computedIntrinsicWidth(layoutRange, isFirstLine); |
| layoutRange.start = intrinsicContent.inlineItemRange.end; |
| maximumLineWidth = std::max(maximumLineWidth, intrinsicContent.logicalWidth); |
| isFirstLine = false; |
| // FIXME: Add support for clear. |
| for (auto* floatBox : intrinsicContent.floats) |
| maximumFloatWidth += geometryForBox(*floatBox).marginBoxWidth(); |
| } |
| return maximumLineWidth + maximumFloatWidth; |
| } |
| |
| void InlineFormattingContext::computeIntrinsicWidthForFormattingRoot(const Box& formattingRoot) |
| { |
| ASSERT(formattingRoot.establishesFormattingContext()); |
| auto constraints = IntrinsicWidthConstraints { }; |
| if (auto fixedWidth = formattingGeometry().fixedValue(formattingRoot.style().logicalWidth())) |
| constraints = { *fixedWidth, *fixedWidth }; |
| else { |
| auto hasInflowOrFloatingContent = is<ContainerBox>(formattingRoot) && downcast<ContainerBox>(formattingRoot).hasInFlowOrFloatingChild(); |
| // The intrinsic sizes of the size containment box are determined as if the element had no content. |
| auto shouldIgnoreChildContent = formattingRoot.isSizeContainmentBox(); |
| if (hasInflowOrFloatingContent && !shouldIgnoreChildContent) |
| constraints = LayoutContext::createFormattingContext(downcast<ContainerBox>(formattingRoot), layoutState())->computedIntrinsicWidthConstraints(); |
| } |
| constraints = formattingGeometry().constrainByMinMaxWidth(formattingRoot, constraints); |
| constraints.expand(geometryForBox(formattingRoot).horizontalMarginBorderAndPadding()); |
| formattingState().setIntrinsicWidthConstraintsForBox(formattingRoot, constraints); |
| } |
| |
| void InlineFormattingContext::computeHorizontalMargin(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints) |
| { |
| auto computedHorizontalMargin = formattingGeometry().computedHorizontalMargin(layoutBox, horizontalConstraints); |
| formattingState().boxGeometry(layoutBox).setHorizontalMargin({ computedHorizontalMargin.start.value_or(0), computedHorizontalMargin.end.value_or(0) }); |
| } |
| |
| void InlineFormattingContext::computeWidthAndMargin(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints) |
| { |
| auto compute = [&](std::optional<LayoutUnit> usedWidth) { |
| if (layoutBox.isFloatingPositioned()) |
| return formattingGeometry().floatingContentWidthAndMargin(layoutBox, horizontalConstraints, { usedWidth, { } }); |
| if (layoutBox.isInlineBlockBox()) |
| return formattingGeometry().inlineBlockContentWidthAndMargin(layoutBox, horizontalConstraints, { usedWidth, { } }); |
| if (layoutBox.isReplacedBox()) |
| return formattingGeometry().inlineReplacedContentWidthAndMargin(downcast<ReplacedBox>(layoutBox), horizontalConstraints, { }, { usedWidth, { } }); |
| ASSERT_NOT_REACHED(); |
| return ContentWidthAndMargin { }; |
| }; |
| |
| auto contentWidthAndMargin = compute({ }); |
| |
| auto availableWidth = horizontalConstraints.logicalWidth; |
| if (auto maxWidth = formattingGeometry().computedMaxWidth(layoutBox, availableWidth)) { |
| auto maxWidthAndMargin = compute(maxWidth); |
| if (contentWidthAndMargin.contentWidth > maxWidthAndMargin.contentWidth) |
| contentWidthAndMargin = maxWidthAndMargin; |
| } |
| |
| auto minWidth = formattingGeometry().computedMinWidth(layoutBox, availableWidth).value_or(0); |
| auto minWidthAndMargin = compute(minWidth); |
| if (contentWidthAndMargin.contentWidth < minWidthAndMargin.contentWidth) |
| contentWidthAndMargin = minWidthAndMargin; |
| |
| auto& boxGeometry = formattingState().boxGeometry(layoutBox); |
| boxGeometry.setContentBoxWidth(contentWidthAndMargin.contentWidth); |
| boxGeometry.setHorizontalMargin({ contentWidthAndMargin.usedMargin.start, contentWidthAndMargin.usedMargin.end }); |
| } |
| |
| void InlineFormattingContext::computeHeightAndMargin(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints) |
| { |
| auto compute = [&](std::optional<LayoutUnit> usedHeight) { |
| if (layoutBox.isFloatingPositioned()) |
| return formattingGeometry().floatingContentHeightAndMargin(layoutBox, horizontalConstraints, { usedHeight }); |
| if (layoutBox.isInlineBlockBox()) |
| return formattingGeometry().inlineBlockContentHeightAndMargin(layoutBox, horizontalConstraints, { usedHeight }); |
| if (layoutBox.isReplacedBox()) |
| return formattingGeometry().inlineReplacedContentHeightAndMargin(downcast<ReplacedBox>(layoutBox), horizontalConstraints, { }, { usedHeight }); |
| ASSERT_NOT_REACHED(); |
| return ContentHeightAndMargin { }; |
| }; |
| |
| auto contentHeightAndMargin = compute({ }); |
| if (auto maxHeight = formattingGeometry().computedMaxHeight(layoutBox)) { |
| auto maxHeightAndMargin = compute(maxHeight); |
| if (contentHeightAndMargin.contentHeight > maxHeightAndMargin.contentHeight) |
| contentHeightAndMargin = maxHeightAndMargin; |
| } |
| |
| if (auto minHeight = formattingGeometry().computedMinHeight(layoutBox)) { |
| auto minHeightAndMargin = compute(minHeight); |
| if (contentHeightAndMargin.contentHeight < minHeightAndMargin.contentHeight) |
| contentHeightAndMargin = minHeightAndMargin; |
| } |
| auto& boxGeometry = formattingState().boxGeometry(layoutBox); |
| boxGeometry.setContentBoxHeight(contentHeightAndMargin.contentHeight); |
| boxGeometry.setVerticalMargin({ contentHeightAndMargin.nonCollapsedMargin.before, contentHeightAndMargin.nonCollapsedMargin.after }); |
| } |
| |
| void InlineFormattingContext::collectContentIfNeeded() |
| { |
| auto& formattingState = this->formattingState(); |
| if (!formattingState.inlineItems().isEmpty()) |
| return; |
| auto inlineItemsBuilder = InlineItemsBuilder { root(), formattingState }; |
| formattingState.addInlineItems(inlineItemsBuilder.build()); |
| } |
| |
| InlineRect InlineFormattingContext::computeGeometryForLineContent(const LineBuilder::LineContent& lineContent) |
| { |
| auto& formattingState = this->formattingState(); |
| auto currentLineIndex = formattingState.lines().size(); |
| |
| auto lineAndLineBox = LineBoxBuilder(*this).build(lineContent, currentLineIndex); |
| auto lineBoxLogicalRect = lineAndLineBox.line.lineBoxLogicalRect(); |
| |
| auto inlineContentBuilder = InlineDisplayContentBuilder { root(), formattingState }; |
| formattingState.addBoxes(inlineContentBuilder.build(lineContent, lineAndLineBox.lineBox, lineBoxLogicalRect, currentLineIndex)); |
| formattingState.addLineBox(WTFMove(lineAndLineBox.lineBox)); |
| formattingState.addLine(lineAndLineBox.line); |
| |
| return lineBoxLogicalRect; |
| } |
| |
| void InlineFormattingContext::invalidateFormattingState() |
| { |
| if (!m_lineDamage) { |
| // Non-empty formatting state with no damage means we are trying to layout a clean tree. |
| // FIXME: Add ASSERT(formattingState().inlineItems().isEmpty()) when all the codepaths are covered. |
| formattingState().clearLineAndBoxes(); |
| return; |
| } |
| // FIXME: Lines and boxes are moved out to under Integration::InlineContent in the integration codepath, so clearLineAndBoxes is no-op. |
| switch (m_lineDamage->type()) { |
| case InlineDamage::Type::NeedsContentUpdateAndLineLayout: |
| formattingState().clearInlineItems(); |
| FALLTHROUGH; |
| case InlineDamage::Type::NeedsLineLayout: |
| formattingState().clearLineAndBoxes(); |
| break; |
| case InlineDamage::Type::NeedsVerticalAdjustment: |
| case InlineDamage::Type::NeedsHorizontalAdjustment: |
| default: |
| ASSERT_NOT_IMPLEMENTED_YET(); |
| break; |
| } |
| } |
| |
| } |
| } |
| |
| #endif |