Skip to content
Permalink
Browse files
[LFC][IFC] Add "line spanning line box start items" to Line
https://bugs.webkit.org/show_bug.cgi?id=231551

Reviewed by Antti Koivisto.

This patch is in preparation for supporting box-decoration-break: clone, where the line spanning
inline boxes may take up space on the line (border/padding).
This patch moves the construction of the spanning inline boxes to an earlier step, from LineBox to Line.

* layout/formattingContexts/inline/InlineLineBuilder.cpp:
(WebCore::Layout::LineBuilder::initialize):


Canonical link: https://commits.webkit.org/243118@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@284323 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
alanbujtas committed Oct 16, 2021
1 parent b94aa41 commit 88599fa339d2d500c34e490894c3d96fb2543ca7
Showing 9 changed files with 147 additions and 81 deletions.
@@ -1,3 +1,17 @@
2021-10-16 Alan Bujtas <zalan@apple.com>

[LFC][IFC] Add "line spanning line box start items" to Line
https://bugs.webkit.org/show_bug.cgi?id=231551

Reviewed by Antti Koivisto.

This patch is in preparation for supporting box-decoration-break: clone, where the line spanning
inline boxes may take up space on the line (border/padding).
This patch moves the construction of the spanning inline boxes to an earlier step, from LineBox to Line.

* layout/formattingContexts/inline/InlineLineBuilder.cpp:
(WebCore::Layout::LineBuilder::initialize):

2021-10-16 Alan Bujtas <zalan@apple.com>

[LFC][IFC] Move overflowing content creation to LineBuilder::initialize
@@ -78,8 +78,7 @@ void InlineDisplayContentBuilder::createBoxesAndUpdateGeometryForLineContent(con
return !lineIndex ? layoutBox.firstLineStyle() : layoutBox.style();
}();

switch (lineRun.type()) {
case InlineItem::Type::Text: {
if (lineRun.isText()) {
auto textRunRect = lineBox.logicalRectForTextRun(lineRun);
textRunRect.moveBy(lineBoxLogicalTopLeft);

@@ -110,9 +109,9 @@ void InlineDisplayContentBuilder::createBoxesAndUpdateGeometryForLineContent(con
, inkOverflow()
, lineRun.expansion()
, InlineDisplay::Box::Text { text->start, text->length, content, adjustedContentToRender(), text->needsHyphen } });
break;
continue;
}
case InlineItem::Type::SoftLineBreak: {
if (lineRun.isSoftLineBreak()) {
auto softLineBreakRunRect = lineBox.logicalRectForTextRun(lineRun);
softLineBreakRunRect.moveBy(lineBoxLogicalTopLeft);

@@ -127,7 +126,7 @@ void InlineDisplayContentBuilder::createBoxesAndUpdateGeometryForLineContent(con
, InlineDisplay::Box::Text { text->start, text->length, downcast<InlineTextBox>(layoutBox).content() } });
break;
}
case InlineItem::Type::HardLineBreak: {
if (lineRun.isHardLineBreak()) {
// Only hard linebreaks have associated layout boxes.
auto lineBreakBoxRect = lineBox.logicalRectForLineBreakBox(layoutBox);
lineBreakBoxRect.moveBy(lineBoxLogicalTopLeft);
@@ -136,9 +135,9 @@ void InlineDisplayContentBuilder::createBoxesAndUpdateGeometryForLineContent(con
auto& boxGeometry = formattingState.boxGeometry(layoutBox);
boxGeometry.setLogicalTopLeft(toLayoutPoint(lineBreakBoxRect.topLeft()));
boxGeometry.setContentBoxHeight(toLayoutUnit(lineBreakBoxRect.height()));
break;
continue;
}
case InlineItem::Type::Box: {
if (lineRun.isBox()) {
ASSERT(layoutBox.isAtomicInlineLevelBox());
auto& boxGeometry = formattingState.boxGeometry(layoutBox);
auto logicalBorderBox = lineBox.logicalBorderBoxForAtomicInlineLevelBox(layoutBox, boxGeometry);
@@ -161,9 +160,9 @@ void InlineDisplayContentBuilder::createBoxesAndUpdateGeometryForLineContent(con
boxes[m_inlineBoxIndexMap.get(&parentInlineBox)].adjustInkOverflow(logicalBorderBox);
};
adjustParentInlineBoxInkOverflow();
break;
continue;
}
case InlineItem::Type::InlineBoxStart: {
if (lineRun.isInlineBoxStart()) {
// This inline box showed up first on this line.
auto& boxGeometry = formattingState.boxGeometry(layoutBox);
auto inlineBoxBorderBox = lineBox.logicalBorderBoxForInlineBox(layoutBox, boxGeometry);
@@ -185,12 +184,9 @@ void InlineDisplayContentBuilder::createBoxesAndUpdateGeometryForLineContent(con
boxGeometry.setContentBoxHeight(contentBoxHeight);
auto contentBoxWidth = logicalRect.width() - (boxGeometry.horizontalBorder() + boxGeometry.horizontalPadding().value_or(0_lu));
boxGeometry.setContentBoxWidth(contentBoxWidth);
break;
}
default:
ASSERT(lineRun.isInlineBoxEnd() || lineRun.isWordBreakOpportunity());
break;
continue;
}
ASSERT(lineRun.isInlineBoxEnd() || lineRun.isWordBreakOpportunity() || lineRun.isLineSpanningInlineBoxStart());
}
}

@@ -35,7 +35,16 @@ namespace Layout {

class InlineItem {
public:
enum class Type : uint8_t { Text, HardLineBreak, SoftLineBreak, WordBreakOpportunity, Box, Float, InlineBoxStart, InlineBoxEnd };
enum class Type : uint8_t {
Text,
HardLineBreak,
SoftLineBreak,
WordBreakOpportunity,
Box,
InlineBoxStart,
InlineBoxEnd,
Float
};
InlineItem(const Box& layoutBox, Type);

Type type() const { return m_type; }
@@ -50,12 +50,17 @@ Line::~Line()
{
}

void Line::initialize()
void Line::initialize(const Vector<InlineItem>& lineSpanningInlineBoxes)
{
m_nonSpanningInlineLevelBoxCount = 0;
m_contentLogicalWidth = { };
m_runs.clear();
resetTrailingContent();
auto appendLineSpanningInlineBoxes = [&] {
for (auto& inlineBoxStartItem : lineSpanningInlineBoxes)
m_runs.append({ inlineBoxStartItem });
};
appendLineSpanningInlineBoxes();
}

void Line::resetTrailingContent()
@@ -192,7 +197,7 @@ void Line::visuallyCollapseHangingOverflow(InlineLayoutUnit horizontalAvailableS
for (auto& run : WTF::makeReversedRange(m_runs)) {
if (!run.shouldTrailingWhitespaceHang())
break;
auto visuallyCollapsibleInlineItem = run.isInlineBoxStart() || run.isInlineBoxEnd() || run.hasTrailingWhitespace();
auto visuallyCollapsibleInlineItem = run.isLineSpanningInlineBoxStart() || run.isInlineBoxStart() || run.isInlineBoxEnd() || run.hasTrailingWhitespace();
if (!visuallyCollapsibleInlineItem)
break;
ASSERT(!run.hasCollapsibleTrailingWhitespace());
@@ -280,7 +285,7 @@ void Line::appendTextContent(const InlineTextItem& inlineTextItem, const RenderS
// provided both spaces are within the same inline formatting context—is collapsed to have zero advance width.
if (run.isText())
return run.hasCollapsibleTrailingWhitespace();
ASSERT(run.isInlineBoxStart() || run.isInlineBoxEnd() || run.isWordBreakOpportunity());
ASSERT(run.isLineSpanningInlineBoxStart() || run.isInlineBoxStart() || run.isInlineBoxEnd() || run.isWordBreakOpportunity());
}
// Leading whitespace.
return true;
@@ -445,7 +450,7 @@ InlineLayoutUnit Line::TrimmableTrailingContent::remove()
// not produce a run since in ::appendText() we see it as a fully collapsible run.
for (auto index = *m_firstTrimmableRunIndex + 1; index < m_runs.size(); ++index) {
auto& run = m_runs[index];
ASSERT(run.isWordBreakOpportunity() || run.isInlineBoxStart() || run.isInlineBoxEnd() || run.isLineBreak());
ASSERT(run.isWordBreakOpportunity() || run.isLineSpanningInlineBoxStart() || run.isInlineBoxStart() || run.isInlineBoxEnd() || run.isLineBreak());
run.moveHorizontally(-trimmableWidth);
}
if (!trimmableRun.textContent()->length) {
@@ -475,8 +480,27 @@ void Line::HangingTrailingContent::add(const InlineTextItem& trailingWhitespace,
m_length += trailingWhitespace.length();
}

inline static Line::Run::Type toLineRunType(InlineItem::Type inlineItemType)
{
switch (inlineItemType) {
case InlineItem::Type::HardLineBreak:
return Line::Run::Type::HardLineBreak;
case InlineItem::Type::WordBreakOpportunity:
return Line::Run::Type::WordBreakOpportunity;
case InlineItem::Type::Box:
return Line::Run::Type::AtomicBox;
case InlineItem::Type::InlineBoxStart:
return Line::Run::Type::InlineBoxStart;
case InlineItem::Type::InlineBoxEnd:
return Line::Run::Type::InlineBoxEnd;
default:
RELEASE_ASSERT_NOT_REACHED();
}
return { };
}

Line::Run::Run(const InlineItem& inlineItem, const RenderStyle& style, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth)
: m_type(inlineItem.type())
: m_type(toLineRunType(inlineItem.type()))
, m_layoutBox(&inlineItem.layoutBox())
, m_logicalLeft(logicalLeft)
, m_logicalWidth(logicalWidth)
@@ -485,22 +509,29 @@ Line::Run::Run(const InlineItem& inlineItem, const RenderStyle& style, InlineLay
}

Line::Run::Run(const InlineItem& zeroWidhtInlineItem, InlineLayoutUnit logicalLeft)
: m_type(zeroWidhtInlineItem.type())
: m_type(toLineRunType(zeroWidhtInlineItem.type()))
, m_layoutBox(&zeroWidhtInlineItem.layoutBox())
, m_logicalLeft(logicalLeft)
{
}

Line::Run::Run(const InlineItem& lineSpanningInlineBoxItem)
: m_type(Type::LineSpanningInlineBoxStart)
, m_layoutBox(&lineSpanningInlineBoxItem.layoutBox())
{
ASSERT(lineSpanningInlineBoxItem.isInlineBoxStart());
}

Line::Run::Run(const InlineSoftLineBreakItem& softLineBreakItem, InlineLayoutUnit logicalLeft)
: m_type(softLineBreakItem.type())
: m_type(Type::SoftLineBreak)
, m_layoutBox(&softLineBreakItem.layoutBox())
, m_logicalLeft(logicalLeft)
, m_textContent({ softLineBreakItem.position(), 1 })
{
}

Line::Run::Run(const InlineTextItem& inlineTextItem, const RenderStyle& style, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth)
: m_type(InlineItem::Type::Text)
: m_type(Type::Text)
, m_layoutBox(&inlineTextItem.layoutBox())
, m_logicalLeft(logicalLeft)
, m_logicalWidth(logicalWidth)
@@ -42,10 +42,12 @@ class Line {
Line(const InlineFormattingContext&);
~Line();

void initialize();
void initialize(const Vector<InlineItem>& lineSpanningInlineBoxes);

void append(const InlineItem&, const RenderStyle&, InlineLayoutUnit logicalWidth);

bool hasContent() const { return !m_runs.isEmpty() && !m_runs.last().isLineSpanningInlineBoxStart(); }

InlineLayoutUnit contentLogicalWidth() const { return m_contentLogicalWidth; }
InlineLayoutUnit contentLogicalRight() const { return m_runs.isEmpty() ? 0.0f : m_runs.last().logicalRight(); }
size_t nonSpanningInlineLevelBoxCount() const { return m_nonSpanningInlineLevelBoxCount; }
@@ -62,15 +64,26 @@ class Line {
void applyRunExpansion(InlineLayoutUnit horizontalAvailableSpace);

struct Run {
bool isText() const { return m_type == InlineItem::Type::Text; }
bool isBox() const { return m_type == InlineItem::Type::Box; }
enum class Type : uint8_t {
Text,
HardLineBreak,
SoftLineBreak,
WordBreakOpportunity,
AtomicBox,
InlineBoxStart,
InlineBoxEnd,
LineSpanningInlineBoxStart
};

bool isText() const { return m_type == Type::Text; }
bool isBox() const { return m_type == Type::AtomicBox; }
bool isLineBreak() const { return isHardLineBreak() || isSoftLineBreak(); }
bool isSoftLineBreak() const { return m_type == InlineItem::Type::SoftLineBreak; }
bool isHardLineBreak() const { return m_type == InlineItem::Type::HardLineBreak; }
bool isWordBreakOpportunity() const { return m_type == InlineItem::Type::WordBreakOpportunity; }
bool isInlineBoxStart() const { return m_type == InlineItem::Type::InlineBoxStart; }
bool isInlineBoxEnd() const { return m_type == InlineItem::Type::InlineBoxEnd; }
auto type() const { return m_type; }
bool isSoftLineBreak() const { return m_type == Type::SoftLineBreak; }
bool isHardLineBreak() const { return m_type == Type::HardLineBreak; }
bool isWordBreakOpportunity() const { return m_type == Type::WordBreakOpportunity; }
bool isInlineBoxStart() const { return m_type == Type::InlineBoxStart; }
bool isLineSpanningInlineBoxStart() const { return m_type == Type::LineSpanningInlineBoxStart; }
bool isInlineBoxEnd() const { return m_type == Type::InlineBoxEnd; }

const Box& layoutBox() const { return *m_layoutBox; }
struct Text {
@@ -101,6 +114,7 @@ class Line {
Run(const InlineSoftLineBreakItem&, InlineLayoutUnit logicalLeft);
Run(const InlineItem&, const RenderStyle&, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth);
Run(const InlineItem&, InlineLayoutUnit logicalLeft);
Run(const InlineItem& lineSpanningInlineBoxItem);

void expand(const InlineTextItem&, InlineLayoutUnit logicalWidth);
void moveHorizontally(InlineLayoutUnit offset) { m_logicalLeft += offset; }
@@ -124,7 +138,7 @@ class Line {
InlineLayoutUnit trailingLetterSpacing() const;
void removeTrailingLetterSpacing();

InlineItem::Type m_type { InlineItem::Type::Text };
Type m_type { Type::Text };
const Box* m_layoutBox { nullptr };
InlineLayoutUnit m_logicalLeft { 0 };
InlineLayoutUnit m_logicalWidth { 0 };
@@ -254,43 +254,6 @@ InlineLayoutUnit LineBoxBuilder::constructAndAlignInlineLevelBoxes(LineBox& line
return !lineIndex ? layoutBox.firstLineStyle() : layoutBox.style();
};

auto createLineSpanningInlineBoxes = [&] {
if (runs.isEmpty())
return;
// An inline box may not necessarily start on the current line:
// <span id=outer>line break<br>this content's parent inline box('outer') <span id=inner>starts on the previous line</span></span>
// We need to make sure that there's an InlineLevelBox for every inline box that's present on the current line.
// In nesting case we need to create InlineLevelBoxes for the inline box ancestors.
// We only have to do it on the first run as any subsequent inline content is either at the same/higher nesting level or
// nested with a [inline box start] run.
auto& firstRun = runs[0];
auto& firstRunParentLayoutBox = firstRun.layoutBox().parent();
// If the parent is the formatting root, we can stop here. This is root inline box content, there's no nesting inline box from the previous line(s)
// unless the inline box closing is forced over to the current line.
// e.g.
// <span>normally the inline box closing forms a continuous content</span>
// <span>unless it's forced to the next line<br></span>
auto firstRunNeedsInlineBox = firstRun.isInlineBoxEnd();
if (!firstRunNeedsInlineBox && isRootLayoutBox(firstRunParentLayoutBox))
return;
Vector<const Box*> layoutBoxesWithoutInlineBoxes;
if (firstRunNeedsInlineBox)
layoutBoxesWithoutInlineBoxes.append(&firstRun.layoutBox());
auto* ancestor = &firstRunParentLayoutBox;
while (!isRootLayoutBox(*ancestor)) {
layoutBoxesWithoutInlineBoxes.append(ancestor);
ancestor = &ancestor->parent();
}
// Construct the missing LineBox::InlineBoxes starting with the topmost layout box.
for (auto* layoutBox : WTF::makeReversedRange(layoutBoxesWithoutInlineBoxes)) {
auto inlineBox = InlineLevelBox::createInlineBox(*layoutBox, styleToUse(*layoutBox), rootInlineBox.logicalLeft(), rootInlineBox.logicalWidth(), InlineLevelBox::LineSpanningInlineBox::Yes);
setInitialVerticalGeometryForInlineBox(inlineBox);
updateCanUseSimplifiedAlignment(inlineBox);
lineBox.addInlineLevelBox(WTFMove(inlineBox));
}
};
createLineSpanningInlineBoxes();

auto lineHasContent = false;
for (auto& run : runs) {
auto& layoutBox = run.layoutBox();
@@ -299,14 +262,16 @@ InlineLayoutUnit LineBoxBuilder::constructAndAlignInlineLevelBoxes(LineBox& line
ASSERT(!lineHasContent);
if (run.isText() || run.isBox() || run.isSoftLineBreak() || run.isHardLineBreak())
return true;
if (run.isLineSpanningInlineBoxStart())
return false;
if (run.isWordBreakOpportunity())
return false;
auto& inlineBoxGeometry = formattingContext().geometryForBox(layoutBox);
// Even negative horizontal margin makes the line "contentful".
if (run.isInlineBoxStart())
return inlineBoxGeometry.marginStart() || inlineBoxGeometry.borderLeft() || inlineBoxGeometry.paddingLeft().value_or(0_lu);
if (run.isInlineBoxEnd())
return inlineBoxGeometry.marginEnd() || inlineBoxGeometry.borderRight() || inlineBoxGeometry.paddingRight().value_or(0_lu);
if (run.isWordBreakOpportunity())
return false;
ASSERT_NOT_REACHED();
return true;
};
@@ -344,6 +309,13 @@ InlineLayoutUnit LineBoxBuilder::constructAndAlignInlineLevelBoxes(LineBox& line
lineBox.addInlineLevelBox(WTFMove(atomicInlineLevelBox));
continue;
}
if (run.isLineSpanningInlineBoxStart()) {
auto inlineBox = InlineLevelBox::createInlineBox(layoutBox, style, logicalLeft, rootInlineBox.logicalWidth(), InlineLevelBox::LineSpanningInlineBox::Yes);
setInitialVerticalGeometryForInlineBox(inlineBox);
updateCanUseSimplifiedAlignment(inlineBox);
lineBox.addInlineLevelBox(WTFMove(inlineBox));
continue;
}
if (run.isInlineBoxStart()) {
// At this point we don't know yet how wide this inline box is. Let's assume it's as long as the line is
// and adjust it later if we come across an inlineBoxEnd run (see below).
@@ -60,8 +60,6 @@ class LineBoxBuilder {
const Box& rootBox() const { return formattingContext().root(); }
LayoutState& layoutState() const { return formattingContext().layoutState(); }

bool isRootLayoutBox(const ContainerBox& containerBox) const { return &containerBox == &rootBox(); }

private:
const InlineFormattingContext& m_inlineFormattingContext;
};

0 comments on commit 88599fa

Please sign in to comment.