Skip to content

Commit

Permalink
[IFC][InlineContentBreaker] Add fast path for text only minimum intri…
Browse files Browse the repository at this point in the history
…nsic width computation

https://bugs.webkit.org/show_bug.cgi?id=260433

Reviewed by Antti Koivisto.

Minimum width computation is heavily used by flex, table, float and out-of-flow content.

~10% progression on PerformanceTests/Layout/line-layout-preferred-width-break-all.html.

* Source/WebCore/layout/formattingContexts/inline/InlineContentBreaker.cpp:
(WebCore::Layout::InlineContentBreaker::processInlineContent):
(WebCore::Layout::InlineContentBreaker::processOverflowingContent const):
(WebCore::Layout::InlineContentBreaker::simplifiedMinimumInstrinsicWidthBreak const):
(WebCore::Layout::InlineContentBreaker::wordBreakBehavior const):
(WebCore::Layout::InlineContentBreaker::ContinuousContent::append):
(WebCore::Layout::InlineContentBreaker::ContinuousContent::appendTextContent):
(WebCore::Layout::InlineContentBreaker::ContinuousContent::reset):
(WebCore::Layout::hasTextRun): Deleted.
* Source/WebCore/layout/formattingContexts/inline/InlineContentBreaker.h:
(WebCore::Layout::InlineContentBreaker::ContinuousContent::hasTextContent const):
(WebCore::Layout::InlineContentBreaker::ContinuousContent::isTextContent const):
(WebCore::Layout::InlineContentBreaker::setIsMinimumInIntrinsicWidthMode):
(WebCore::Layout::InlineContentBreaker::isMinimumInIntrinsicWidthMode const):
(WebCore::Layout::InlineContentBreaker::setIsInIntrinsicWidthMode): Deleted.
(WebCore::Layout::InlineContentBreaker::isInIntrinsicWidthMode const): Deleted.
* Source/WebCore/layout/formattingContexts/inline/InlineLineBuilder.cpp:
(WebCore::Layout::LineBuilder::handleInlineContent):
* Source/WebCore/layout/formattingContexts/inline/TextOnlySimpleLineBuilder.cpp:
(WebCore::Layout::TextOnlySimpleLineBuilder::handleOverflowingTextContent):
* Source/WebCore/layout/formattingContexts/inline/text/TextUtil.cpp:
(WebCore::Layout::TextUtil::width):
* Source/WebCore/layout/formattingContexts/inline/text/TextUtil.h:

Canonical link: https://commits.webkit.org/267093@main
  • Loading branch information
alanbaradlay committed Aug 21, 2023
1 parent 021a0fc commit 015d4c6
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,6 @@ static inline bool hasLeadingTextContent(const InlineContentBreaker::ContinuousC
return false;
}

static inline bool hasTextRun(const InlineContentBreaker::ContinuousContent& continuousContent)
{
// <span>text</span> is considered a text run even with the [inline box start][inline box end] inline items.
// Based on standards commit boundary rules it would be enough to check the first inline item.
for (auto& run : continuousContent.runs()) {
if (run.inlineItem.isText())
return true;
}
return false;
}

static inline std::optional<size_t> nextTextRunIndex(const InlineContentBreaker::ContinuousContent::RunList& runs, size_t startIndex)
{
for (auto index = startIndex + 1; index < runs.size(); ++index) {
Expand Down Expand Up @@ -111,7 +100,11 @@ static inline std::optional<size_t> firstTextRunIndex(const InlineContentBreaker
InlineContentBreaker::Result InlineContentBreaker::processInlineContent(const ContinuousContent& candidateContent, const LineStatus& lineStatus)
{
ASSERT(!std::isnan(lineStatus.availableWidth));
ASSERT(candidateContent.logicalWidth() > lineStatus.availableWidth);
ASSERT(isMinimumInIntrinsicWidthMode() || candidateContent.logicalWidth() > lineStatus.availableWidth);

if (auto result = simplifiedMinimumInstrinsicWidthBreak(candidateContent, lineStatus))
return *result;

auto result = processOverflowingContent(candidateContent, lineStatus);
if (result.action == Result::Action::Wrap && lineStatus.trailingSoftHyphenWidth && hasLeadingTextContent(candidateContent)) {
// A trailing soft hyphen with a wrapped text content turns into a visible hyphen.
Expand All @@ -123,9 +116,8 @@ InlineContentBreaker::Result InlineContentBreaker::processInlineContent(const Co
return result;
}

InlineContentBreaker::Result InlineContentBreaker::processOverflowingContent(const ContinuousContent& overflowContent, const LineStatus& lineStatus) const
InlineContentBreaker::Result InlineContentBreaker::processOverflowingContent(const ContinuousContent& continuousContent, const LineStatus& lineStatus) const
{
auto continuousContent = ContinuousContent { overflowContent };
ASSERT(!continuousContent.runs().isEmpty());

ASSERT(continuousContent.logicalWidth() > lineStatus.availableWidth);
Expand Down Expand Up @@ -166,7 +158,7 @@ InlineContentBreaker::Result InlineContentBreaker::processOverflowingContent(con
return *result;

size_t overflowingRunIndex = 0;
if (hasTextRun(continuousContent)) {
if (continuousContent.hasTextContent()) {
auto tryBreakingContentWithText = [&]() -> std::optional<Result> {
// 1. This text content is not breakable.
// 2. This breakable text content does not fit at all. Not even the first glyph. This is a very special case.
Expand Down Expand Up @@ -254,6 +246,33 @@ InlineContentBreaker::Result InlineContentBreaker::processOverflowingContent(con
return { Result::Action::Keep, IsEndOfLine::No };
}

std::optional<InlineContentBreaker::Result> InlineContentBreaker::simplifiedMinimumInstrinsicWidthBreak(const ContinuousContent& candidateContent, const LineStatus& lineStatus) const
{
if (!isMinimumInIntrinsicWidthMode() || !candidateContent.isTextOnlyContent())
return { };

auto& leadingInlineTextItem = downcast<InlineTextItem>(candidateContent.runs().first().inlineItem);
auto& style = leadingInlineTextItem.style();
if (!TextUtil::isWrappingAllowed(style))
return Result { Result::Action::Keep, IsEndOfLine::No };

if (!lineStatus.hasContent) {
auto breakBehavior = wordBreakBehavior(style, { });
if (breakBehavior.isEmpty())
return Result { Result::Action::Keep, IsEndOfLine::No };

if (breakBehavior.containsAny({ WordBreakRule::AtArbitraryPositionWithinWords, WordBreakRule::AtArbitraryPosition })) {
auto firstCharacterLength = TextUtil::firstUserPerceivedCharacterLength(leadingInlineTextItem);
if (leadingInlineTextItem.length() == firstCharacterLength)
return Result { Result::Action::Keep, IsEndOfLine::Yes };
auto firstCharacterWidth = TextUtil::width(leadingInlineTextItem, style.fontCascade(), leadingInlineTextItem.start(), leadingInlineTextItem.start() + firstCharacterLength, { }, TextUtil::UseTrailingWhitespaceMeasuringOptimization::No);
return Result { Result::Action::Break, IsEndOfLine::Yes, Result::PartialTrailingContent { { }, PartialRun { firstCharacterLength, firstCharacterWidth }, { } } };
}
return { };
}
return Result { !lineStatus.trailingSoftHyphenWidth ? Result::Action::Wrap : Result::Action::RevertToLastNonOverflowingWrapOpportunity, IsEndOfLine::Yes };
}

static std::optional<size_t> findTrailingRunIndex(const InlineContentBreaker::ContinuousContent::RunList& runs, size_t breakableRunIndex)
{
// When the breaking position is at the beginning of the run, the trailing run is the previous one.
Expand Down Expand Up @@ -768,7 +787,7 @@ OptionSet<InlineContentBreaker::WordBreakRule> InlineContentBreaker::wordBreakBe
// OverflowWrap::BreakWord/Anywhere An otherwise unbreakable sequence of characters may be broken at an arbitrary point if there are no otherwise-acceptable break points in the line.
// Note that this applies to content where CSS properties (e.g. WordBreak::KeepAll) make it unbreakable.
// Soft wrap opportunities introduced by overflow-wrap/word-wrap: break-word are not considered when calculating min-content intrinsic sizes.
auto overflowWrapBreakWordIsApplicable = !isInIntrinsicWidthMode();
auto overflowWrapBreakWordIsApplicable = !isMinimumInIntrinsicWidthMode();
if (((overflowWrapBreakWordIsApplicable && style.overflowWrap() == OverflowWrap::BreakWord) || style.overflowWrap() == OverflowWrap::Anywhere) && !hasWrapOpportunityAtPreviousPosition)
return includeHyphenationIfAllowed(WordBreakRule::AtArbitraryPosition);
// Breaking is forbidden within “words”.
Expand All @@ -793,6 +812,7 @@ void InlineContentBreaker::ContinuousContent::resetTrailingTrimmableContent()
void InlineContentBreaker::ContinuousContent::append(const InlineItem& inlineItem, const RenderStyle& style, InlineLayoutUnit logicalWidth)
{
ASSERT(inlineItem.isBox() || inlineItem.isInlineBoxStart() || inlineItem.isInlineBoxEnd());
m_isTextOnlyContent = false;
appendToRunList(inlineItem, style, logicalWidth);
if (inlineItem.isBox()) {
// Inline boxes (whitespace-> <span></span>) do not prevent the trailing content from getting trimmed/hung
Expand All @@ -803,6 +823,7 @@ void InlineContentBreaker::ContinuousContent::append(const InlineItem& inlineIte

void InlineContentBreaker::ContinuousContent::appendTextContent(const InlineTextItem& inlineTextItem, const RenderStyle& style, InlineLayoutUnit logicalWidth)
{
m_hasTextContent = true;
// https://www.w3.org/TR/css-text-4/#white-space-phase-2
auto isTrailingHangingContent = inlineTextItem.isWhitespace() && TextUtil::shouldTrailingWhitespaceHang(style);
if (isTrailingHangingContent)
Expand Down Expand Up @@ -839,6 +860,8 @@ void InlineContentBreaker::ContinuousContent::reset()
m_trailingTrimmableWidth = { };
m_hangingContentWidth = { };
m_runs.clear();
m_hasTextContent = false;
m_isTextOnlyContent = true;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ class InlineContentBreaker {
InlineLayoutUnit hangingContentWidth() const { return m_hangingContentWidth; }
bool hasTrimmableContent() const { return trailingTrimmableWidth() || leadingTrimmableWidth(); }
bool hasHangingContent() const { return hangingContentWidth(); }
bool hasTextContent() const { return m_hasTextContent; }
bool isTextOnlyContent() const { return m_isTextOnlyContent; }
bool isFullyTrimmable() const;
bool isHangingContent() const { return hangingContentWidth() == logicalWidth(); }

Expand Down Expand Up @@ -115,6 +117,8 @@ class InlineContentBreaker {
InlineLayoutUnit m_leadingTrimmableWidth { 0.f };
InlineLayoutUnit m_trailingTrimmableWidth { 0.f };
InlineLayoutUnit m_hangingContentWidth { 0.f };
bool m_hasTextContent { false };
bool m_isTextOnlyContent { true };
};

struct LineStatus {
Expand All @@ -129,7 +133,7 @@ class InlineContentBreaker {
};
Result processInlineContent(const ContinuousContent&, const LineStatus&);
void setHyphenationDisabled(bool hyphenationIsDisabled) { n_hyphenationIsDisabled = hyphenationIsDisabled; }
void setIsInIntrinsicWidthMode(bool isInIntrinsicWidthMode) { m_isInIntrinsicWidthMode = isInIntrinsicWidthMode; }
void setIsMinimumInIntrinsicWidthMode(bool isMinimumInIntrinsicWidthMode) { m_isMinimumInIntrinsicWidthMode = isMinimumInIntrinsicWidthMode; }
static bool isWrappingAllowed(const ContinuousContent::Run&);

private:
Expand All @@ -151,6 +155,7 @@ class InlineContentBreaker {
std::optional<BreakingPosition> breakingPosition { }; // Where we actually break this overflowing content.
};
OverflowingTextContent processOverflowingContentWithText(const ContinuousContent&, const LineStatus&) const;
std::optional<Result> simplifiedMinimumInstrinsicWidthBreak(const ContinuousContent&, const LineStatus&) const;
std::optional<PartialRun> tryBreakingTextRun(const ContinuousContent::RunList& runs, const CandidateTextRunForBreaking&, InlineLayoutUnit availableWidth, const LineStatus&) const;
std::optional<OverflowingTextContent::BreakingPosition> tryBreakingOverflowingRun(const LineStatus&, const ContinuousContent::RunList&, size_t overflowingRunIndex, InlineLayoutUnit nonOverflowingContentWidth) const;
std::optional<OverflowingTextContent::BreakingPosition> tryBreakingPreviousNonOverflowingRuns(const LineStatus&, const ContinuousContent::RunList&, size_t overflowingRunIndex, InlineLayoutUnit nonOverflowingContentWidth) const;
Expand All @@ -163,10 +168,10 @@ class InlineContentBreaker {
AtHyphenationOpportunities = 1 << 2
};
OptionSet<WordBreakRule> wordBreakBehavior(const RenderStyle&, bool hasWrapOpportunityAtPreviousPosition) const;
bool isInIntrinsicWidthMode() const { return m_isInIntrinsicWidthMode; }
bool isMinimumInIntrinsicWidthMode() const { return m_isMinimumInIntrinsicWidthMode; }

private:
bool m_isInIntrinsicWidthMode { false };
bool m_isMinimumInIntrinsicWidthMode { false };
bool n_hyphenationIsDisabled { false };
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1068,7 +1068,7 @@ LineBuilder::Result LineBuilder::handleInlineContent(const InlineItemRange& layo
!m_wrapOpportunityList.isEmpty()
};
m_inlineContentBreaker.setHyphenationDisabled(shouldDisableHyphenation(root().style(), m_successiveHyphenatedLineCount));
m_inlineContentBreaker.setIsInIntrinsicWidthMode(isInIntrinsicWidthMode());
m_inlineContentBreaker.setIsMinimumInIntrinsicWidthMode(intrinsicWidthMode() == IntrinsicWidthMode::Minimum);
lineBreakingResult = m_inlineContentBreaker.processInlineContent(continuousInlineContent, lineStatus);
}
auto lineGainsNewContent = lineBreakingResult.action == InlineContentBreaker::Result::Action::Keep || lineBreakingResult.action == InlineContentBreaker::Result::Action::Break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ TextOnlyLineBreakResult TextOnlySimpleLineBuilder::handleOverflowingTextContent(
auto lineBreakingResult = InlineContentBreaker::Result { InlineContentBreaker::Result::Action::Keep, InlineContentBreaker::IsEndOfLine::No, { }, { } };
if (candidateContent.logicalWidth() > availableWidth) {
auto lineStatus = InlineContentBreaker::LineStatus { m_line.contentLogicalRight(), availableWidth, m_line.trimmableTrailingWidth(), m_line.trailingSoftHyphenWidth(), m_line.isTrailingRunFullyTrimmable(), m_line.hasContentOrListMarker(), !m_wrapOpportunityList.isEmpty() };
m_inlineContentBreaker.setIsInIntrinsicWidthMode(isInIntrinsicWidthMode());
m_inlineContentBreaker.setIsMinimumInIntrinsicWidthMode(intrinsicWidthMode() == IntrinsicWidthMode::Minimum);
lineBreakingResult = m_inlineContentBreaker.processInlineContent(candidateContent, lineStatus);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ InlineLayoutUnit TextUtil::width(const InlineTextItem& inlineTextItem, const Fon
return TextUtil::width(inlineTextItem, fontCascade, inlineTextItem.start(), inlineTextItem.end(), contentLogicalLeft);
}

InlineLayoutUnit TextUtil::width(const InlineTextItem& inlineTextItem, const FontCascade& fontCascade, unsigned from, unsigned to, InlineLayoutUnit contentLogicalLeft)
InlineLayoutUnit TextUtil::width(const InlineTextItem& inlineTextItem, const FontCascade& fontCascade, unsigned from, unsigned to, InlineLayoutUnit contentLogicalLeft, UseTrailingWhitespaceMeasuringOptimization useTrailingWhitespaceMeasuringOptimization)
{
RELEASE_ASSERT(from >= inlineTextItem.start());
RELEASE_ASSERT(to <= inlineTextItem.end());
Expand All @@ -106,7 +106,7 @@ InlineLayoutUnit TextUtil::width(const InlineTextItem& inlineTextItem, const Fon
return std::isnan(width) ? 0.0f : std::isinf(width) ? maxInlineLayoutUnit() : width;
}
}
return width(inlineTextItem.inlineTextBox(), fontCascade, from, to, contentLogicalLeft);
return width(inlineTextItem.inlineTextBox(), fontCascade, from, to, contentLogicalLeft, useTrailingWhitespaceMeasuringOptimization);
}

InlineLayoutUnit TextUtil::trailingWhitespaceWidth(const InlineTextBox& inlineTextBox, const FontCascade& fontCascade, size_t startPosition, size_t endPosition)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ class InlineTextItem;

class TextUtil {
public:
static InlineLayoutUnit width(const InlineTextItem&, const FontCascade&, InlineLayoutUnit contentLogicalLeft);
static InlineLayoutUnit width(const InlineTextItem&, const FontCascade&, unsigned from, unsigned to, InlineLayoutUnit contentLogicalLeft);

enum class UseTrailingWhitespaceMeasuringOptimization : bool { No, Yes };
static InlineLayoutUnit width(const InlineTextItem&, const FontCascade&, InlineLayoutUnit contentLogicalLeft);
static InlineLayoutUnit width(const InlineTextItem&, const FontCascade&, unsigned from, unsigned to, InlineLayoutUnit contentLogicalLeft, UseTrailingWhitespaceMeasuringOptimization = UseTrailingWhitespaceMeasuringOptimization::Yes);
static InlineLayoutUnit width(const InlineTextBox&, const FontCascade&, unsigned from, unsigned to, InlineLayoutUnit contentLogicalLeft, UseTrailingWhitespaceMeasuringOptimization = UseTrailingWhitespaceMeasuringOptimization::Yes);

static InlineLayoutUnit trailingWhitespaceWidth(const InlineTextBox&, const FontCascade&, size_t startPosition, size_t endPosition);
Expand Down

0 comments on commit 015d4c6

Please sign in to comment.