blob: c4e357cbb8f0b6ff7156cdc4b006933316973824 [file] [log] [blame]
/*
* Copyright (C) 2021 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 "InlineDisplayContentBuilder.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "FontCascade.h"
#include "LayoutBoxGeometry.h"
#include "LayoutInitialContainingBlock.h"
#include "RuntimeEnabledFeatures.h"
#include "TextUtil.h"
namespace WebCore {
namespace Layout {
static inline OptionSet<InlineDisplay::Box::PositionWithinInlineLevelBox> isFirstLastBox(const InlineLevelBox& inlineBox)
{
auto positionWithinInlineLevelBox = OptionSet<InlineDisplay::Box::PositionWithinInlineLevelBox> { };
if (inlineBox.isFirstBox())
positionWithinInlineLevelBox.add(InlineDisplay::Box::PositionWithinInlineLevelBox::First);
if (inlineBox.isLastBox())
positionWithinInlineLevelBox.add(InlineDisplay::Box::PositionWithinInlineLevelBox::Last);
return positionWithinInlineLevelBox;
}
InlineDisplayContentBuilder::InlineDisplayContentBuilder(const ContainerBox& formattingContextRoot, InlineFormattingState& formattingState)
: m_formattingContextRoot(formattingContextRoot)
, m_formattingState(formattingState)
{
}
DisplayBoxes InlineDisplayContentBuilder::build(const LineBuilder::LineContent& lineContent, const LineBox& lineBox, const InlineRect& lineBoxLogicalRect, const size_t lineIndex)
{
DisplayBoxes boxes;
boxes.reserveInitialCapacity(lineContent.runs.size() + lineBox.nonRootInlineLevelBoxes().size() + 1);
m_lineIndex = lineIndex;
// Every line starts with a root box, even the empty ones.
auto rootInlineBoxRect = lineBox.logicalRectForRootInlineBox();
rootInlineBoxRect.moveBy(lineBoxLogicalRect.topLeft());
boxes.append({ m_lineIndex, InlineDisplay::Box::Type::RootInlineBox, root(), UBIDI_DEFAULT_LTR, rootInlineBoxRect, rootInlineBoxRect, { }, { }, lineBox.rootInlineBox().hasContent() });
auto contentNeedsBidiReordering = !lineContent.visualOrderList.isEmpty();
if (contentNeedsBidiReordering)
processBidiContent(lineContent, lineBox, lineBoxLogicalRect.topLeft(), boxes);
else
processNonBidiContent(lineContent, lineBox, lineBoxLogicalRect.topLeft(), boxes);
processOverflownRunsForEllipsis(boxes, lineBoxLogicalRect.right());
collectInkOverflowForInlineBoxes(lineBox, boxes);
return boxes;
}
static inline void addBoxShadowInkOverflow(const RenderStyle& style, InlineRect& inkOverflow)
{
auto topBoxShadow = LayoutUnit { };
auto bottomBoxShadow = LayoutUnit { };
style.getBoxShadowBlockDirectionExtent(topBoxShadow, bottomBoxShadow);
auto leftBoxShadow = LayoutUnit { };
auto rightBoxShadow = LayoutUnit { };
style.getBoxShadowInlineDirectionExtent(leftBoxShadow, rightBoxShadow);
inkOverflow.inflate(InlineLayoutUnit { topBoxShadow }, InlineLayoutUnit { rightBoxShadow }, InlineLayoutUnit { bottomBoxShadow }, InlineLayoutUnit { leftBoxShadow });
}
void InlineDisplayContentBuilder::appendTextDisplayBox(const Line::Run& lineRun, const InlineRect& textRunRect, DisplayBoxes& boxes)
{
ASSERT(lineRun.textContent() && is<InlineTextBox>(lineRun.layoutBox()));
auto& layoutBox = lineRun.layoutBox();
auto& style = !m_lineIndex ? layoutBox.firstLineStyle() : layoutBox.style();
auto inkOverflow = [&] {
auto initialContaingBlockSize = RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled()
? formattingState().layoutState().viewportSize()
: formattingState().layoutState().geometryForBox(layoutBox.initialContainingBlock()).contentBox().size();
auto strokeOverflow = ceilf(style.computedStrokeWidth(ceiledIntSize(initialContaingBlockSize)));
auto inkOverflow = textRunRect;
inkOverflow.inflate(strokeOverflow);
auto letterSpacing = style.fontCascade().letterSpacing();
if (letterSpacing < 0) {
// Last letter's negative spacing shrinks logical rect. Push it to ink overflow.
inkOverflow.expand(-letterSpacing, { });
}
return inkOverflow;
};
auto content = downcast<InlineTextBox>(layoutBox).content();
auto text = lineRun.textContent();
auto adjustedContentToRender = [&] {
return text->needsHyphen ? makeString(content.substring(text->start, text->length), style.hyphenString()) : String();
};
boxes.append({ m_lineIndex
, InlineDisplay::Box::Type::Text
, layoutBox
, lineRun.bidiLevel()
, textRunRect
, inkOverflow()
, lineRun.expansion()
, InlineDisplay::Box::Text { text->start, text->length, content, adjustedContentToRender(), text->needsHyphen } });
}
void InlineDisplayContentBuilder::appendSoftLineBreakDisplayBox(const Line::Run& lineRun, const InlineRect& softLineBreakRunRect, DisplayBoxes& boxes)
{
ASSERT(lineRun.textContent() && is<InlineTextBox>(lineRun.layoutBox()));
auto& layoutBox = lineRun.layoutBox();
auto& text = lineRun.textContent();
boxes.append({ m_lineIndex
, InlineDisplay::Box::Type::SoftLineBreak
, layoutBox
, lineRun.bidiLevel()
, softLineBreakRunRect
, softLineBreakRunRect
, lineRun.expansion()
, InlineDisplay::Box::Text { text->start, text->length, downcast<InlineTextBox>(layoutBox).content() } });
}
void InlineDisplayContentBuilder::appendHardLineBreakDisplayBox(const Line::Run& lineRun, const InlineRect& lineBreakBoxRect, DisplayBoxes& boxes)
{
auto& layoutBox = lineRun.layoutBox();
boxes.append({ m_lineIndex
, InlineDisplay::Box::Type::LineBreakBox
, layoutBox
, lineRun.bidiLevel()
, lineBreakBoxRect
, lineBreakBoxRect
, lineRun.expansion()
, { } });
auto& boxGeometry = formattingState().boxGeometry(layoutBox);
boxGeometry.setLogicalTopLeft(toLayoutPoint(lineBreakBoxRect.topLeft()));
boxGeometry.setContentBoxHeight(toLayoutUnit(lineBreakBoxRect.height()));
}
void InlineDisplayContentBuilder::appendAtomicInlineLevelDisplayBox(const Line::Run& lineRun, const InlineRect& borderBoxRect, DisplayBoxes& boxes)
{
ASSERT(lineRun.layoutBox().isAtomicInlineLevelBox());
auto& layoutBox = lineRun.layoutBox();
// FIXME: Add ink overflow support for atomic inline level boxes (e.g. box shadow).
boxes.append({ m_lineIndex
, InlineDisplay::Box::Type::AtomicInlineLevelBox
, layoutBox
, lineRun.bidiLevel()
, borderBoxRect
, borderBoxRect
, lineRun.expansion()
, { } });
// Note that inline boxes are relative to the line and their top position can be negative.
// Atomic inline boxes are all set. Their margin/border/content box geometries are already computed. We just have to position them here.
auto& boxGeometry = formattingState().boxGeometry(layoutBox);
boxGeometry.setLogicalTopLeft(toLayoutPoint(borderBoxRect.topLeft()));
auto adjustParentInlineBoxInkOverflow = [&] {
auto& parentInlineBox = layoutBox.parent();
if (&parentInlineBox == &root()) {
// We don't collect ink overflow for the root inline box.
return;
}
RELEASE_ASSERT(m_inlineBoxIndexMap.contains(&parentInlineBox));
auto boxInkOverflow = borderBoxRect;
addBoxShadowInkOverflow(!m_lineIndex ? layoutBox.firstLineStyle() : layoutBox.style(), boxInkOverflow);
boxes[m_inlineBoxIndexMap.get(&parentInlineBox)].adjustInkOverflow(boxInkOverflow);
};
adjustParentInlineBoxInkOverflow();
}
void InlineDisplayContentBuilder::appendInlineBoxDisplayBox(const Line::Run& lineRun, const InlineLevelBox& inlineBox, const InlineRect& inlineBoxBorderBox, bool linehasContent, DisplayBoxes& boxes)
{
auto& layoutBox = lineRun.layoutBox();
if (linehasContent) {
auto inkOverflow = [&] {
auto inkOverflow = inlineBoxBorderBox;
addBoxShadowInkOverflow(!m_lineIndex ? layoutBox.firstLineStyle() : layoutBox.style(), inkOverflow);
return inkOverflow;
};
// FIXME: It's expected to not have any boxes on empty lines. We should reconsider this.
m_inlineBoxIndexMap.add(&layoutBox, boxes.size());
ASSERT(inlineBox.isInlineBox());
ASSERT(inlineBox.isFirstBox());
boxes.append({ m_lineIndex
, InlineDisplay::Box::Type::NonRootInlineBox
, layoutBox
, lineRun.bidiLevel()
, inlineBoxBorderBox
, inkOverflow()
, { }
, { }
, inlineBox.hasContent()
, isFirstLastBox(inlineBox) });
}
auto inlineBoxSize = LayoutSize { LayoutUnit::fromFloatCeil(inlineBoxBorderBox.width()), LayoutUnit::fromFloatCeil(inlineBoxBorderBox.height()) };
auto logicalRect = Rect { LayoutPoint { inlineBoxBorderBox.topLeft() }, inlineBoxSize };
auto& boxGeometry = formattingState().boxGeometry(layoutBox);
boxGeometry.setLogicalTopLeft(logicalRect.topLeft());
auto contentBoxHeight = logicalRect.height() - (boxGeometry.verticalBorder() + boxGeometry.verticalPadding().value_or(0_lu));
boxGeometry.setContentBoxHeight(contentBoxHeight);
auto contentBoxWidth = logicalRect.width() - (boxGeometry.horizontalBorder() + boxGeometry.horizontalPadding().value_or(0_lu));
boxGeometry.setContentBoxWidth(contentBoxWidth);
}
void InlineDisplayContentBuilder::appendSpanningInlineBoxDisplayBox(const Line::Run& lineRun, const InlineLevelBox& inlineBox, const InlineRect& inlineBoxBorderBox, DisplayBoxes& boxes)
{
auto& layoutBox = lineRun.layoutBox();
m_inlineBoxIndexMap.add(&layoutBox, boxes.size());
auto inkOverflow = [&] {
auto inkOverflow = inlineBoxBorderBox;
addBoxShadowInkOverflow(!m_lineIndex ? layoutBox.firstLineStyle() : layoutBox.style(), inkOverflow);
return inkOverflow;
};
ASSERT(!inlineBox.isFirstBox());
boxes.append({ m_lineIndex
, InlineDisplay::Box::Type::NonRootInlineBox
, layoutBox
, lineRun.bidiLevel()
, inlineBoxBorderBox
, inkOverflow()
, { }
, { }
, inlineBox.hasContent()
, isFirstLastBox(inlineBox) });
auto inlineBoxSize = LayoutSize { LayoutUnit::fromFloatCeil(inlineBoxBorderBox.width()), LayoutUnit::fromFloatCeil(inlineBoxBorderBox.height()) };
auto logicalRect = Rect { LayoutPoint { inlineBoxBorderBox.topLeft() }, inlineBoxSize };
// Middle or end of the inline box. Let's stretch the box as needed.
auto& boxGeometry = formattingState().boxGeometry(layoutBox);
auto enclosingBorderBoxRect = BoxGeometry::borderBoxRect(boxGeometry);
enclosingBorderBoxRect.expandToContain(logicalRect);
boxGeometry.setLogicalLeft(enclosingBorderBoxRect.left());
boxGeometry.setContentBoxHeight(enclosingBorderBoxRect.height() - (boxGeometry.verticalBorder() + boxGeometry.verticalPadding().value_or(0_lu)));
boxGeometry.setContentBoxWidth(enclosingBorderBoxRect.width() - (boxGeometry.horizontalBorder() + boxGeometry.horizontalPadding().value_or(0_lu)));
}
void InlineDisplayContentBuilder::processNonBidiContent(const LineBuilder::LineContent& lineContent, const LineBox& lineBox, const InlineLayoutPoint& lineBoxLogicalTopLeft, DisplayBoxes& boxes)
{
// Create the inline boxes on the current line. This is mostly text and atomic inline boxes.
for (auto& lineRun : lineContent.runs) {
auto displayBoxRect = [&] {
auto& layoutBox = lineRun.layoutBox();
auto logicalRect = InlineRect { };
if (lineRun.isText() || lineRun.isSoftLineBreak())
logicalRect = lineBox.logicalRectForTextRun(lineRun);
else if (lineRun.isHardLineBreak())
logicalRect = lineBox.logicalRectForLineBreakBox(layoutBox);
else if (lineRun.isBox())
logicalRect = lineBox.logicalBorderBoxForAtomicInlineLevelBox(layoutBox, formattingState().boxGeometry(layoutBox));
else if (lineRun.isInlineBoxStart() || lineRun.isLineSpanningInlineBoxStart())
logicalRect = lineBox.logicalBorderBoxForInlineBox(layoutBox, formattingState().boxGeometry(layoutBox));
else
ASSERT_NOT_REACHED();
logicalRect.moveBy(lineBoxLogicalTopLeft);
return logicalRect;
};
if (lineRun.isText()) {
appendTextDisplayBox(lineRun, displayBoxRect(), boxes);
continue;
}
if (lineRun.isSoftLineBreak()) {
appendSoftLineBreakDisplayBox(lineRun, displayBoxRect(), boxes);
continue;
}
if (lineRun.isHardLineBreak()) {
appendHardLineBreakDisplayBox(lineRun, displayBoxRect(), boxes);
continue;
}
if (lineRun.isBox()) {
appendAtomicInlineLevelDisplayBox(lineRun, displayBoxRect(), boxes);
continue;
}
if (lineRun.isInlineBoxStart()) {
// This inline box showed up first on this line.
appendInlineBoxDisplayBox(lineRun, lineBox.inlineLevelBoxForLayoutBox(lineRun.layoutBox()), displayBoxRect(), lineBox.hasContent(), boxes);
continue;
}
if (lineRun.isLineSpanningInlineBoxStart()) {
if (!lineBox.hasContent()) {
// When a spanning inline box (e.g. <div>text<span><br></span></div>) lands on an empty line
// (empty here means no content at all including line breaks, not just visually empty) then we
// don't extend the spanning line box over to this line -also there is no next line in cases like this.
continue;
}
appendSpanningInlineBoxDisplayBox(lineRun, lineBox.inlineLevelBoxForLayoutBox(lineRun.layoutBox()), displayBoxRect(), boxes);
continue;
}
ASSERT(lineRun.isInlineBoxEnd() || lineRun.isWordBreakOpportunity());
}
}
void InlineDisplayContentBuilder::processBidiContent(const LineBuilder::LineContent& lineContent, const LineBox& lineBox, const InlineLayoutPoint& lineBoxLogicalTopLeft, DisplayBoxes& boxes)
{
// Create the inline boxes on the current line. This is mostly text and atomic inline boxes.
auto& runs = lineContent.runs;
ASSERT(lineContent.visualOrderList.size() == runs.size());
auto rootInlineBoxRect = lineBox.logicalRectForRootInlineBox();
auto contentRightInVisualOrder = lineBoxLogicalTopLeft.x();
// First visual run's initial content position depends on the block's inline direction.
if (!root().style().isLeftToRightDirection()) {
// FIXME: This needs the block end position instead of the lineLogicalWidth.
contentRightInVisualOrder += lineContent.lineLogicalWidth - rootInlineBoxRect.width();
}
// Adjust the content start position with the (text)aligment offset (root inline box has the aligment offset and not the individual runs).
contentRightInVisualOrder += rootInlineBoxRect.left();
for (size_t i = 0; i < runs.size(); ++i) {
auto visualIndex = lineContent.visualOrderList[i];
auto& lineRun = runs[visualIndex];
auto& layoutBox = lineRun.layoutBox();
auto displayBoxRect = [&] {
auto logicalRect = InlineRect { };
auto marginStart = std::optional<LayoutUnit> { };
if (lineRun.isText() || lineRun.isSoftLineBreak())
logicalRect = lineBox.logicalRectForTextRun(lineRun);
else if (lineRun.isHardLineBreak())
logicalRect = lineBox.logicalRectForLineBreakBox(layoutBox);
else {
auto& boxGeometry = formattingState().boxGeometry(layoutBox);
if (lineRun.isBox()) {
marginStart = boxGeometry.marginStart();
logicalRect = lineBox.logicalBorderBoxForAtomicInlineLevelBox(layoutBox, boxGeometry);
} else if (lineRun.isInlineBoxStart()) {
marginStart = boxGeometry.marginStart();
logicalRect = lineBox.logicalBorderBoxForInlineBox(layoutBox, boxGeometry);
} else if (lineRun.isLineSpanningInlineBoxStart())
logicalRect = lineBox.logicalBorderBoxForInlineBox(layoutBox, boxGeometry);
else
ASSERT_NOT_REACHED();
}
logicalRect.moveVertically(lineBoxLogicalTopLeft.y());
// Use the distance from the logical previous run to place the display box horizontally in visual terms.
auto* logicalPreviousRun = visualIndex ? &runs[visualIndex - 1] : nullptr;
// Certain css properties (e.g. word-spacing) may introduce a gap between runs.
auto distanceFromLogicalPreviousRun = logicalPreviousRun ? lineRun.logicalLeft() - logicalPreviousRun->logicalRight() : lineRun.logicalLeft();
auto visualOrderRect = logicalRect;
auto contentLeft = contentRightInVisualOrder + distanceFromLogicalPreviousRun + marginStart.value_or(0);
visualOrderRect.setLeft(contentLeft);
return visualOrderRect;
};
if (lineRun.isText()) {
auto textRunRect = displayBoxRect();
appendTextDisplayBox(lineRun, textRunRect, boxes);
contentRightInVisualOrder = textRunRect.right();
continue;
}
if (lineRun.isSoftLineBreak()) {
appendSoftLineBreakDisplayBox(lineRun, displayBoxRect(), boxes);
continue;
}
if (lineRun.isHardLineBreak()) {
appendHardLineBreakDisplayBox(lineRun, displayBoxRect(), boxes);
continue;
}
if (lineRun.isBox()) {
auto borderBoxRect = displayBoxRect();
appendAtomicInlineLevelDisplayBox(lineRun, borderBoxRect, boxes);
contentRightInVisualOrder = borderBoxRect.right();
continue;
}
if (lineRun.isInlineBoxStart()) {
// This inline box showed up first on this line.
appendInlineBoxDisplayBox(lineRun, lineBox.inlineLevelBoxForLayoutBox(lineRun.layoutBox()), displayBoxRect(), lineBox.hasContent(), boxes);
contentRightInVisualOrder += lineRun.logicalWidth();
continue;
}
if (lineRun.isLineSpanningInlineBoxStart()) {
if (!lineBox.hasContent()) {
// When a spanning inline box (e.g. <div>text<span><br></span></div>) lands on an empty line
// (empty here means no content at all including line breaks, not just visually empty) then we
// don't extend the spanning line box over to this line -also there is no next line in cases like this.
continue;
}
appendSpanningInlineBoxDisplayBox(lineRun, lineBox.inlineLevelBoxForLayoutBox(lineRun.layoutBox()), displayBoxRect(), boxes);
// The content right edge should not include the entire inline box here (including its content and right edge).
contentRightInVisualOrder += lineRun.logicalWidth();
continue;
}
if (lineRun.isInlineBoxEnd()) {
contentRightInVisualOrder += lineRun.logicalWidth();
continue;
}
ASSERT(lineRun.isWordBreakOpportunity());
}
}
void InlineDisplayContentBuilder::processOverflownRunsForEllipsis(DisplayBoxes& boxes, InlineLayoutUnit lineBoxLogicalRight)
{
if (root().style().textOverflow() != TextOverflow::Ellipsis)
return;
auto& rootInlineBox = boxes[0];
ASSERT(rootInlineBox.isRootInlineBox());
auto rootInlineBoxRect = rootInlineBox.logicalRect();
if (rootInlineBoxRect.right() <= lineBoxLogicalRight) {
ASSERT(boxes.last().logicalRight() <= lineBoxLogicalRight);
return;
}
static MainThreadNeverDestroyed<const AtomString> ellipsisStr(&horizontalEllipsis, 1);
auto ellipsisRun = WebCore::TextRun { ellipsisStr->string() };
auto ellipsisWidth = !m_lineIndex ? root().firstLineStyle().fontCascade().width(ellipsisRun) : root().style().fontCascade().width(ellipsisRun);
auto firstTruncatedBoxIndex = boxes.size();
for (auto index = boxes.size(); index--;) {
auto& displayBox = boxes[index];
if (displayBox.logicalLeft() >= lineBoxLogicalRight) {
// Fully overflown boxes are collapsed.
displayBox.truncate();
continue;
}
// We keep truncating content until after we can accommodate the ellipsis content
// 1. fully truncate in case of inline level boxes (ie non-text content) or if ellipsis content is wider than the overflowing one.
// 2. partially truncated to make room for the ellipsis box.
auto availableRoomForEllipsis = lineBoxLogicalRight - displayBox.logicalLeft();
if (availableRoomForEllipsis <= ellipsisWidth) {
// Can't accommodate the ellipsis content here. We need to truncate non-overflowing boxes too.
displayBox.truncate();
continue;
}
auto truncatedWidth = InlineLayoutUnit { };
if (displayBox.isText()) {
auto text = *displayBox.text();
// FIXME: Check if it needs adjustment for RTL direction.
truncatedWidth = TextUtil::breakWord(downcast<InlineTextBox>(displayBox.layoutBox()), text.start(), text.length(), displayBox.logicalWidth(), availableRoomForEllipsis - ellipsisWidth, { }, displayBox.style().fontCascade()).logicalWidth;
}
displayBox.truncate(truncatedWidth);
firstTruncatedBoxIndex = index;
break;
}
ASSERT(firstTruncatedBoxIndex < boxes.size());
// Collapse truncated runs.
auto contentRight = boxes[firstTruncatedBoxIndex].logicalRight();
for (auto index = firstTruncatedBoxIndex + 1; index < boxes.size(); ++index)
boxes[index].moveHorizontally(contentRight - boxes[index].logicalLeft());
// And append the ellipsis box as the trailing item.
auto ellispisBoxRect = InlineRect { rootInlineBoxRect.top(), contentRight, ellipsisWidth, rootInlineBoxRect.height() };
boxes.append({ m_lineIndex
, InlineDisplay::Box::Type::Ellipsis
, root()
, UBIDI_DEFAULT_LTR
, ellispisBoxRect
, ellispisBoxRect
, { }
, InlineDisplay::Box::Text { 0, 1, ellipsisStr->string() } });
}
void InlineDisplayContentBuilder::collectInkOverflowForInlineBoxes(const LineBox& lineBox, DisplayBoxes& boxes)
{
if (m_inlineBoxIndexMap.isEmpty() || !lineBox.hasContent()) {
// This line has no inline box (only root, but we don't collect ink overflow for the root inline box atm)
return;
}
auto& nonRootInlineLevelBoxes = lineBox.nonRootInlineLevelBoxes();
// Visit the inline boxes and propagate ink overflow to their parents -except to the root inline box.
// (e.g. <span style="font-size: 10px;">Small font size<span style="font-size: 300px;">Larger font size. This overflows the top most span.</span></span>).
for (size_t index = nonRootInlineLevelBoxes.size(); index--;) {
if (!nonRootInlineLevelBoxes[index].isInlineBox())
continue;
auto& inlineBox = nonRootInlineLevelBoxes[index].layoutBox();
auto& parentInlineBox = inlineBox.parent();
if (&parentInlineBox == &root())
continue;
RELEASE_ASSERT(m_inlineBoxIndexMap.contains(&inlineBox) && m_inlineBoxIndexMap.contains(&parentInlineBox));
auto& inkOverflow = boxes[m_inlineBoxIndexMap.get(&inlineBox)].inkOverflow();
auto& parentDisplayBox = boxes[m_inlineBoxIndexMap.get(&parentInlineBox)];
parentDisplayBox.adjustInkOverflow(inkOverflow);
}
}
}
}
#endif