Skip to content

Commit

Permalink
[IFC] Turn LineLayoutResult into a cacheable object
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=260045

Reviewed by Antti Koivisto.

This is in preparation for sharing the result of line building between min/max and inline layout codepaths.
(Make some function static so that they don't accidentally access to moved out m_line)

* Source/WebCore/layout/formattingContexts/inline/AbstractLineBuilder.h:
* Source/WebCore/layout/formattingContexts/inline/InlineFormattingGeometry.cpp:
(WebCore::Layout::InlineFormattingGeometry::contentLeftAfterLastLine const):
(WebCore::Layout::InlineFormattingGeometry::horizontalAlignmentOffset):
(WebCore::Layout::InlineFormattingGeometry::horizontalAlignmentOffset const): Deleted.
* Source/WebCore/layout/formattingContexts/inline/InlineFormattingGeometry.h:
* Source/WebCore/layout/formattingContexts/inline/InlineLine.cpp:
(WebCore::Layout::Line::close):
* Source/WebCore/layout/formattingContexts/inline/InlineLine.h:
(WebCore::Layout::Line::contentNeedsBidiReordering const):
(WebCore::Layout::Line::hangingTrailingContentWidth const):
(WebCore::Layout::Line::isHangingTrailingContentWhitespace const):
* Source/WebCore/layout/formattingContexts/inline/InlineLineBuilder.cpp:
(WebCore::Layout::computedVisualOrder):
(WebCore::Layout::horizontalAlignmentOffset):
(WebCore::Layout::isLastLineWithInlineContent):
(WebCore::Layout::inlineBaseDirectionForLineContent):
(WebCore::Layout::LineBuilder::layoutInlineContent):
(WebCore::Layout::LineBuilder::placeInlineAndFloatContent):
(WebCore::Layout::LineBuilder::isLastLineWithInlineContent const): Deleted.
(WebCore::Layout::LineBuilder::inlineBaseDirectionForLineContent const): Deleted.
(WebCore::Layout::LineBuilder::horizontalAlignmentOffset const): Deleted.
* Source/WebCore/layout/formattingContexts/inline/InlineLineBuilder.h:
* Source/WebCore/layout/formattingContexts/inline/TextOnlyLineBuilder.cpp:
(WebCore::Layout::horizontalAlignmentOffset):
(WebCore::Layout::TextOnlyLineBuilder::layoutInlineContent):
(WebCore::Layout::TextOnlyLineBuilder::horizontalAlignmentOffset const): Deleted.
* Source/WebCore/layout/formattingContexts/inline/TextOnlyLineBuilder.h:

Canonical link: https://commits.webkit.org/266853@main
  • Loading branch information
alanbaradlay committed Aug 13, 2023
1 parent cd25edd commit 715601c
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct LineLayoutResult {
using SuspendedFloatList = Vector<const Box*>;

InlineItemRange inlineItemRange;
const Line::RunList& inlineContent;
const Line::RunList inlineContent;

struct FloatContent {
PlacedFloatList placedFloats;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ InlineLayoutUnit InlineFormattingGeometry::contentLeftAfterLastLine(const Constr
lineBoxWidth -= floatOffset;
}
lineBoxLeft += textIndent;
auto rootInlineBoxLeft = horizontalAlignmentOffset(lineBoxWidth, IsLastLineOrAfterLineBreak::Yes);
auto rootInlineBoxLeft = horizontalAlignmentOffset(formattingContext().root().style(), lineBoxWidth, IsLastLineOrAfterLineBreak::Yes);
return lineBoxLeft + rootInlineBoxLeft;
}

Expand Down Expand Up @@ -394,12 +394,11 @@ FloatingContext::Constraints InlineFormattingGeometry::floatConstraintsForLine(I
return floatingContext.constraints(logicalTopCandidate, logicalBottomCandidate, FloatingContext::MayBeAboveLastFloat::Yes);
}

InlineLayoutUnit InlineFormattingGeometry::horizontalAlignmentOffset(InlineLayoutUnit horizontalAvailableSpace, IsLastLineOrAfterLineBreak isLastLineOrAfterLineBreak, std::optional<TextDirection> inlineBaseDirectionOverride) const
InlineLayoutUnit InlineFormattingGeometry::horizontalAlignmentOffset(const RenderStyle& rootStyle, InlineLayoutUnit horizontalAvailableSpace, IsLastLineOrAfterLineBreak isLastLineOrAfterLineBreak, std::optional<TextDirection> inlineBaseDirectionOverride)
{
if (horizontalAvailableSpace <= 0)
return { };

auto& rootStyle = formattingContext().root().style();
auto isLeftToRightDirection = inlineBaseDirectionOverride.value_or(rootStyle.direction()) == TextDirection::LTR;

auto computedHorizontalAlignment = [&] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class InlineFormattingGeometry : public FormattingGeometry {
void adjustMarginStartForListMarker(const ElementBox&, LayoutUnit nestedListMarkerMarginStart, InlineLayoutUnit rootInlineBoxOffset) const;

enum class IsLastLineOrAfterLineBreak : bool { No, Yes };
InlineLayoutUnit horizontalAlignmentOffset(InlineLayoutUnit horizontalAvailableSpace, IsLastLineOrAfterLineBreak, std::optional<TextDirection> inlineBaseDirectionOverride = std::nullopt) const;
static InlineLayoutUnit horizontalAlignmentOffset(const RenderStyle& rootStyle, InlineLayoutUnit horizontalAvailableSpace, IsLastLineOrAfterLineBreak, std::optional<TextDirection> inlineBaseDirectionOverride = std::nullopt);

private:
InlineLayoutUnit contentLeftAfterLastLine(const ConstraintsForInFlowContent&, std::optional<InlineLayoutUnit> lastLineLogicalBottom, const FloatingContext&) const;
Expand Down
17 changes: 13 additions & 4 deletions Source/WebCore/layout/formattingContexts/inline/InlineLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@ Line::Line(const InlineFormattingContext& inlineFormattingContext)
{
}

Line::~Line()
{
}

void Line::initialize(const Vector<InlineItem>& lineSpanningInlineBoxes, bool isFirstFormattedLine)
{
m_isFirstFormattedLine = isFirstFormattedLine;
Expand Down Expand Up @@ -95,6 +91,19 @@ void Line::resetTrailingContent()
m_trailingSoftHyphenWidth = { };
}

Line::Result Line::close()
{
auto contentLogicalRight = this->contentLogicalRight();
return { WTFMove(m_runs)
, contentLogicalWidth()
, contentLogicalRight
, !!m_hangingContent.trailingWhitespaceLength()
, m_hangingContent.trailingWidth()
, m_hasNonDefaultBidiLevelRun
, m_nonSpanningInlineLevelBoxCount
};
}

void Line::applyRunExpansion(InlineLayoutUnit horizontalAvailableSpace)
{
ASSERT(formattingContext().root().style().textAlign() == TextAlignMode::Justify || formattingContext().root().style().textAlignLast() == TextAlignLast::Justify);
Expand Down
23 changes: 17 additions & 6 deletions Source/WebCore/layout/formattingContexts/inline/InlineLine.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ enum class IntrinsicWidthMode;
class Line {
public:
Line(const InlineFormattingContext&);
~Line();
~Line() = default;

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

Expand All @@ -51,18 +51,18 @@ class Line {
bool hasContent() const;
bool hasContentOrListMarker() const;

bool contentNeedsBidiReordering() const { return m_hasNonDefaultBidiLevelRun; }

InlineLayoutUnit contentLogicalWidth() const { return m_contentLogicalWidth; }
InlineLayoutUnit contentLogicalRight() const { return lastRunLogicalRight() + m_clonedEndDecorationWidthForInlineBoxRuns; }

bool contentNeedsBidiReordering() const { return m_hasNonDefaultBidiLevelRun; }
size_t nonSpanningInlineLevelBoxCount() const { return m_nonSpanningInlineLevelBoxCount; }
InlineLayoutUnit hangingTrailingContentWidth() const { return m_hangingContent.trailingWidth(); }
bool isHangingTrailingContentWhitespace() const { return !!m_hangingContent.trailingWhitespaceLength(); }


InlineLayoutUnit trimmableTrailingWidth() const { return m_trimmableTrailingContent.width(); }
bool isTrailingRunFullyTrimmable() const { return m_trimmableTrailingContent.isTrailingRunFullyTrimmable(); }

InlineLayoutUnit hangingTrailingContentWidth() const { return m_hangingContent.trailingWidth(); }
bool isHangingTrailingContentWhitespace() const { return !!m_hangingContent.trailingWhitespaceLength(); }

std::optional<InlineLayoutUnit> trailingSoftHyphenWidth() const { return m_trailingSoftHyphenWidth; }
void addTrailingHyphen(InlineLayoutUnit hyphenLogicalWidth);

Expand Down Expand Up @@ -186,6 +186,17 @@ class Line {
using InlineBoxListWithClonedDecorationEnd = HashMap<const Box*, InlineLayoutUnit>;
const InlineBoxListWithClonedDecorationEnd& inlineBoxListWithClonedDecorationEnd() const { return m_inlineBoxListWithClonedDecorationEnd; }

struct Result {
RunList runs;
InlineLayoutUnit contentLogicalWidth { 0.f };
InlineLayoutUnit contentLogicalRight { 0.f };
bool isHangingTrailingContentWhitespace { false };
InlineLayoutUnit hangingTrailingContentWidth { 0.f };
bool contentNeedsBidiReordering { false };
size_t nonSpanningInlineLevelBoxCount { 0 };
};
Result close();

private:
InlineLayoutUnit lastRunLogicalRight() const { return m_runs.isEmpty() ? 0.0f : m_runs.last().logicalRight(); }

Expand Down
144 changes: 70 additions & 74 deletions Source/WebCore/layout/formattingContexts/inline/InlineLineBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,8 @@ static inline StringBuilder toString(const Line::RunList& runs)
return lineContentBuilder;
}

static inline Vector<int32_t> computedVisualOrder(const Line& line)
static inline Vector<int32_t> computedVisualOrder(const Line::RunList& lineRuns, Vector<int32_t>& visualOrderList)
{
if (!line.contentNeedsBidiReordering())
return { };

auto& lineRuns = line.runs();
Vector<UBiDiLevel> runLevels;
runLevels.reserveInitialCapacity(lineRuns.size());

Expand All @@ -90,7 +86,7 @@ static inline Vector<int32_t> computedVisualOrder(const Line& line)
runIndexOffsetMap.uncheckedAppend(accumulatedOffset);
}

Vector<int32_t> visualOrderList(runLevels.size());
visualOrderList.resizeToFit(runLevels.size());
ubidi_reorderVisual(runLevels.data(), runLevels.size(), visualOrderList.data());
if (hasOpaqueRun) {
ASSERT(visualOrderList.size() == runIndexOffsetMap.size());
Expand All @@ -100,6 +96,59 @@ static inline Vector<int32_t> computedVisualOrder(const Line& line)
return visualOrderList;
}

static InlineLayoutUnit horizontalAlignmentOffset(const RenderStyle& rootStyle, bool isLastLine, const Line::RunList& runs, InlineLayoutUnit contentLogicalRight, InlineLayoutUnit lineLogicalRight, InlineLayoutUnit hangingTrailingWidth, TextDirection inlineBaseDirectionForLineContent)
{
if (runs.isEmpty())
return { };

// Depending on the line’s alignment/justification, the hanging glyph can be placed outside the line box.
if (hangingTrailingWidth) {
ASSERT(!runs.isEmpty());
// If white-space is set to pre-wrap, the UA must (unconditionally) hang this sequence, unless the sequence is followed
// by a forced line break, in which case it must conditionally hang the sequence is instead.
// Note that end of last line in a paragraph is considered a forced break.
auto isConditionalHanging = runs.last().isLineBreak() || isLastLine;
// In some cases, a glyph at the end of a line can conditionally hang: it hangs only if it does not otherwise fit in the line prior to justification.
if (isConditionalHanging) {
// FIXME: Conditional hanging needs partial overflow trimming at glyph boundary, one by one until they fit.
contentLogicalRight = std::min(contentLogicalRight, lineLogicalRight);
} else
contentLogicalRight -= hangingTrailingWidth;
}
auto isLastLineOrAfterLineBreak = isLastLine || (!runs.isEmpty() && runs.last().isLineBreak()) ? InlineFormattingGeometry::IsLastLineOrAfterLineBreak::Yes : InlineFormattingGeometry::IsLastLineOrAfterLineBreak::No;
return InlineFormattingGeometry::horizontalAlignmentOffset(rootStyle, lineLogicalRight - contentLogicalRight, isLastLineOrAfterLineBreak, inlineBaseDirectionForLineContent);
}

static bool isLastLineWithInlineContent(const InlineItemRange& lineRange, const InlineItems& inlineItems, size_t lastInlineItemIndex, bool hasPartialTrailingContent)
{
if (hasPartialTrailingContent)
return false;
if (lineRange.endIndex() == lastInlineItemIndex) {
// We must have only committed trailing (overconstraining) floats on the line when the range is empty.
return !lineRange.isEmpty();
}
// Omit floats to see if this is the last line with inline content.
for (auto i = lastInlineItemIndex; i--;) {
if (!inlineItems[i].isFloat())
return i == lineRange.endIndex() - 1;
}
// There has to be at least one non-float item.
ASSERT_NOT_REACHED();
return false;
}

static TextDirection inlineBaseDirectionForLineContent(const Line::RunList& runs, const RenderStyle& rootStyle, std::optional<PreviousLine> previousLine)
{
ASSERT(!runs.isEmpty());
auto shouldUseBlockDirection = rootStyle.unicodeBidi() != UnicodeBidi::Plaintext;
if (shouldUseBlockDirection)
return rootStyle.direction();
// A previous line ending with a line break (<br> or preserved \n) introduces a new unicode paragraph with its own direction.
if (previousLine && !previousLine->endsWithLineBreak)
return previousLine->inlineBaseDirection;
return TextUtil::directionForTextContent(toString(runs));
}

static inline bool endsWithSoftWrapOpportunity(const InlineTextItem& currentTextItem, const InlineTextItem& nextInlineTextItem)
{
ASSERT(!nextInlineTextItem.isWhitespace());
Expand Down Expand Up @@ -318,29 +367,33 @@ LineLayoutResult LineBuilder::layoutInlineContent(const LineInput& lineInput, co
auto previousLineEndsWithLineBreak = !previousLine ? std::nullopt : std::make_optional(previousLine->endsWithLineBreak);
initialize(lineInput.initialLogicalRect, initialConstraintsForLine(lineInput.initialLogicalRect, previousLineEndsWithLineBreak), lineInput.needsLayoutRange, previousLine);
auto lineContent = placeInlineAndFloatContent(lineInput.needsLayoutRange);
auto result = m_line.close();

if (isInIntrinsicWidthMode()) {
return { lineContent.range
, m_line.runs()
, WTFMove(result.runs)
, { WTFMove(m_placedFloats), WTFMove(m_suspendedFloats), { } }
, { { }, m_line.contentLogicalWidth(), { }, lineContent.overflowLogicalWidth }
, { { }, result.contentLogicalWidth, { }, lineContent.overflowLogicalWidth }
, { m_lineLogicalRect.topLeft(), { }, { }, { } }
};
}

auto isLastLine = isLastLineWithInlineContent(lineContent.range, lineInput.needsLayoutRange.endIndex(), lineContent.partialTrailingContentLength);
auto inlineBaseDirection = m_line.runs().isEmpty() ? TextDirection::LTR : inlineBaseDirectionForLineContent();
auto contentLogicalLeft = horizontalAlignmentOffset(isLastLine);
auto isLastLine = isLastLineWithInlineContent(lineContent.range, m_inlineItems, lineInput.needsLayoutRange.endIndex(), lineContent.partialTrailingContentLength);
auto inlineBaseDirection = result.runs.isEmpty() ? TextDirection::LTR : inlineBaseDirectionForLineContent(result.runs, rootStyle(), m_previousLine);
auto contentLogicalLeft = horizontalAlignmentOffset(rootStyle(), isLastLine, result.runs, result.contentLogicalRight, m_lineLogicalRect.width(), result.hangingTrailingContentWidth, inlineBaseDirection);
Vector<int32_t> visualOrderList;
if (result.contentNeedsBidiReordering)
computedVisualOrder(result.runs, visualOrderList);

return { lineContent.range
, m_line.runs()
, WTFMove(result.runs)
, { WTFMove(m_placedFloats), WTFMove(m_suspendedFloats), m_lineIsConstrainedByFloat }
, { contentLogicalLeft, m_line.contentLogicalWidth(), contentLogicalLeft + m_line.contentLogicalRight(), lineContent.overflowLogicalWidth }
, { contentLogicalLeft, result.contentLogicalWidth, contentLogicalLeft + result.contentLogicalRight, lineContent.overflowLogicalWidth }
, { m_lineLogicalRect.topLeft(), m_lineLogicalRect.width(), m_lineInitialLogicalRect.left() + m_initialIntrusiveFloatsWidth, m_initialLetterClearGap }
, { !m_line.isHangingTrailingContentWhitespace(), m_line.hangingTrailingContentWidth() }
, { computedVisualOrder(m_line), inlineBaseDirection }
, { !result.isHangingTrailingContentWhitespace, result.hangingTrailingContentWidth }
, { WTFMove(visualOrderList), inlineBaseDirection }
, { isFirstFormattedLine() ? LineLayoutResult::IsFirstLast::FirstFormattedLine::WithinIFC : LineLayoutResult::IsFirstLast::FirstFormattedLine::No, isLastLine }
, m_line.nonSpanningInlineLevelBoxCount()
, result.nonSpanningInlineLevelBoxCount
, lineContent.range.isEmpty() ? std::make_optional(m_lineLogicalRect.top() + m_candidateInlineContentEnclosingHeight) : std::nullopt
};
}
Expand Down Expand Up @@ -522,7 +575,7 @@ LineContent LineBuilder::placeInlineAndFloatContent(const InlineItemRange& needs
ASSERT(lineContent.range.endIndex() <= needsLayoutRange.endIndex());

auto handleLineEnding = [&] {
auto isLastLine = isLastLineWithInlineContent(lineContent.range, needsLayoutRange.endIndex(), lineContent.partialTrailingContentLength);
auto isLastLine = isLastLineWithInlineContent(lineContent.range, m_inlineItems, needsLayoutRange.endIndex(), lineContent.partialTrailingContentLength);
auto horizontalAvailableSpace = m_lineLogicalRect.width();
auto& rootStyle = this->rootStyle();

Expand Down Expand Up @@ -1346,63 +1399,6 @@ size_t LineBuilder::rebuildLineForTrailingSoftHyphen(const InlineItemRange& layo
return committedCount;
}

bool LineBuilder::isLastLineWithInlineContent(const InlineItemRange& lineRange, size_t lastInlineItemIndex, bool hasPartialTrailingContent) const
{
if (hasPartialTrailingContent)
return false;
if (lineRange.endIndex() == lastInlineItemIndex) {
// We must have only committed trailing (overconstraining) floats on the line when the range is empty.
return !lineRange.isEmpty();
}
// Omit floats to see if this is the last line with inline content.
for (auto i = lastInlineItemIndex; i--;) {
if (!m_inlineItems[i].isFloat())
return i == lineRange.endIndex() - 1;
}
// There has to be at least one non-float item.
ASSERT_NOT_REACHED();
return false;
}

TextDirection LineBuilder::inlineBaseDirectionForLineContent() const
{
ASSERT(!m_line.runs().isEmpty());
auto shouldUseBlockDirection = rootStyle().unicodeBidi() != UnicodeBidi::Plaintext;
if (shouldUseBlockDirection)
return rootStyle().direction();
// A previous line ending with a line break (<br> or preserved \n) introduces a new unicode paragraph with its own direction.
if (m_previousLine && !m_previousLine->endsWithLineBreak)
return m_previousLine->inlineBaseDirection;
return TextUtil::directionForTextContent(toString(m_line.runs()));
}

InlineLayoutUnit LineBuilder::horizontalAlignmentOffset(bool isLastLine) const
{
if (m_line.runs().isEmpty())
return { };

// Depending on the line’s alignment/justification, the hanging glyph can be placed outside the line box.
auto& runs = m_line.runs();
auto contentLogicalRight = m_line.contentLogicalRight();
auto lineLogicalRight = m_lineLogicalRect.width();

if (auto hangingTrailingWidth = m_line.hangingTrailingContentWidth()) {
ASSERT(!runs.isEmpty());
// If white-space is set to pre-wrap, the UA must (unconditionally) hang this sequence, unless the sequence is followed
// by a forced line break, in which case it must conditionally hang the sequence is instead.
// Note that end of last line in a paragraph is considered a forced break.
auto isConditionalHanging = runs.last().isLineBreak() || isLastLine;
// In some cases, a glyph at the end of a line can conditionally hang: it hangs only if it does not otherwise fit in the line prior to justification.
if (isConditionalHanging) {
// FIXME: Conditional hanging needs partial overflow trimming at glyph boundary, one by one until they fit.
contentLogicalRight = std::min(contentLogicalRight, lineLogicalRight);
} else
contentLogicalRight -= hangingTrailingWidth;
}
auto isLastLineOrAfterLineBreak = isLastLine || (!runs.isEmpty() && runs.last().isLineBreak()) ? InlineFormattingGeometry::IsLastLineOrAfterLineBreak::Yes : InlineFormattingGeometry::IsLastLineOrAfterLineBreak::No;
return formattingContext().formattingGeometry().horizontalAlignmentOffset(lineLogicalRight - contentLogicalRight, isLastLineOrAfterLineBreak, inlineBaseDirectionForLineContent());
}

const ElementBox& LineBuilder::root() const
{
return formattingContext().root();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,6 @@ class LineBuilder : public AbstractLineBuilder {
std::optional<InitialLetterOffsets> adjustLineRectForInitialLetterIfApplicable(const Box& floatBox);

InlineLayoutUnit inlineItemWidth(const InlineItem&, InlineLayoutUnit contentLogicalLeft) const;
bool isLastLineWithInlineContent(const InlineItemRange& lineRange, size_t lastInlineItemIndex, bool hasPartialTrailingContent) const;

TextDirection inlineBaseDirectionForLineContent() const;
InlineLayoutUnit horizontalAlignmentOffset(bool isLastLine) const;

bool isFloatLayoutSuspended() const { return !m_suspendedFloats.isEmpty(); }
bool shouldTryToPlaceFloatBox(const Box& floatBox, LayoutUnit floatBoxMarginBoxWidth, MayOverConstrainLine) const;
Expand Down
Loading

0 comments on commit 715601c

Please sign in to comment.