Diff
Modified: trunk/Source/WebCore/ChangeLog (267923 => 267924)
--- trunk/Source/WebCore/ChangeLog 2020-10-03 15:02:22 UTC (rev 267923)
+++ trunk/Source/WebCore/ChangeLog 2020-10-03 15:12:17 UTC (rev 267924)
@@ -1,5 +1,33 @@
2020-10-03 Zalan Bujtas <[email protected]>
+ [LFC][IFC][Soft hyphen] InlineContentBreaker should return Action::Revert when the trailing soft hyphen does not fit
+ https://bugs.webkit.org/show_bug.cgi?id=217269
+
+ Reviewed by Antti Koivisto.
+
+ A trailing soft hyphen turns action "Push" to action "Revert" when the hyphen overflows.
+ e.g <div>1­2­3­4</div>
+ line has: 123­ and when '4' overflows InlineContentBreaker normally returns with Action::Push ('4' is pushed over to the next line).
+ However Action::Push turns the trailing soft hyphen into a visible hyphen and now we need to check if 'Push' is actually a 'Revert' instead.
+
+ * layout/inlineformatting/InlineContentBreaker.cpp:
+ (WebCore::Layout::InlineContentBreaker::processInlineContent):
+ (WebCore::Layout::InlineContentBreaker::processOverflowingContent const):
+ (WebCore::Layout::InlineContentBreaker::tryBreakingTextRun const):
+ * layout/inlineformatting/InlineContentBreaker.h:
+ * layout/inlineformatting/InlineLine.cpp:
+ (WebCore::Layout::Line::initialize):
+ (WebCore::Layout::Line::appendWith):
+ (WebCore::Layout::Line::appendTextContent):
+ (WebCore::Layout::Line::appendNonReplacedInlineBox):
+ (WebCore::Layout::Line::appendLineBreak):
+ * layout/inlineformatting/InlineLine.h:
+ (WebCore::Layout::Line::trailingSoftHyphenWidth const):
+ * layout/inlineformatting/InlineLineBuilder.cpp:
+ (WebCore::Layout::LineBuilder::handleFloatsAndInlineContent):
+
+2020-10-03 Zalan Bujtas <[email protected]>
+
Floating-point math causes shrink-wrapped content to line wrap sometimes
https://bugs.webkit.org/show_bug.cgi?id=217136
<rdar://problem/69801790>
Modified: trunk/Source/WebCore/layout/inlineformatting/InlineContentBreaker.cpp (267923 => 267924)
--- trunk/Source/WebCore/layout/inlineformatting/InlineContentBreaker.cpp 2020-10-03 15:02:22 UTC (rev 267923)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineContentBreaker.cpp 2020-10-03 15:12:17 UTC (rev 267924)
@@ -159,7 +159,7 @@
if (auto lastLineWrapOpportunityIndex = lastWrapOpportunityIndex(candidateContent.runs())) {
auto isEligibleLineWrapOpportunity = [&] (auto& candidateItem) {
// Just check for leading collapsible whitespace for now.
- if (!lineStatus.lineIsEmpty || !candidateItem.isText() || !downcast<InlineTextItem>(candidateItem).isWhitespace())
+ if (!lineStatus.isEmpty || !candidateItem.isText() || !downcast<InlineTextItem>(candidateItem).isWhitespace())
return true;
return shouldKeepBeginningOfLineWhitespace(candidateItem.style());
};
@@ -169,6 +169,10 @@
m_hasWrapOpportunityAtPreviousPosition = true;
}
}
+ } else if (result.action == Result::Action::Push && lineStatus.trailingSoftHyphenWidth) {
+ // A trailing soft hyphen may turn action "Push" to action "Revert".
+ if (*lineStatus.trailingSoftHyphenWidth > lineStatus.availableWidth && isTextContentOnly(candidateContent))
+ result = { Result::Action::RevertToLastNonOverflowingWrapOpportunity, IsEndOfLine::Yes };
}
return result;
}
@@ -192,7 +196,7 @@
if (continuousContent.nonCollapsibleLogicalWidth() <= lineStatus.availableWidth)
return { Result::Action::Keep, IsEndOfLine };
// Now check if we can trim the line too.
- if (lineStatus.lineHasFullyCollapsibleTrailingRun && continuousContent.isFullyCollapsible()) {
+ if (lineStatus.hasFullyCollapsibleTrailingRun && continuousContent.isFullyCollapsible()) {
// If this new content is fully collapsible, it should surely fit.
return { Result::Action::Keep, IsEndOfLine };
}
@@ -214,8 +218,8 @@
// We tried to break the content but the available space can't even accommodate the first character.
// 1. Push the content over to the next line when we've got content on the line already.
// 2. Keep the first character on the empty line (or keep the whole run if it has only one character).
- if (!lineStatus.lineIsEmpty)
- return { Result::Action::Push, IsEndOfLine::Yes, { } };
+ if (!lineStatus.isEmpty)
+ return { Result::Action::Push, IsEndOfLine::Yes };
auto leadingTextRunIndex = *firstTextRunIndex(continuousContent);
auto& inlineTextItem = downcast<InlineTextItem>(continuousContent.runs()[leadingTextRunIndex].inlineItem);
ASSERT(inlineTextItem.length());
@@ -230,7 +234,7 @@
}
}
// If we are not allowed to break this overflowing content, we still need to decide whether keep it or push it to the next line.
- if (lineStatus.lineIsEmpty) {
+ if (lineStatus.isEmpty) {
ASSERT(!m_hasWrapOpportunityAtPreviousPosition);
return { Result::Action::Keep, IsEndOfLine::No };
}
@@ -359,7 +363,6 @@
return { };
unsigned leftSideLength = runLength;
- // FIXME: We might want to cache the hyphen width.
auto& fontCascade = style.fontCascade();
auto hyphenWidth = InlineLayoutUnit { fontCascade.width(TextRun { StringView { style.hyphenString() } }) };
if (!findLastBreakablePosition) {
Modified: trunk/Source/WebCore/layout/inlineformatting/InlineContentBreaker.h (267923 => 267924)
--- trunk/Source/WebCore/layout/inlineformatting/InlineContentBreaker.h 2020-10-03 15:02:22 UTC (rev 267923)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineContentBreaker.h 2020-10-03 15:12:17 UTC (rev 267924)
@@ -51,7 +51,9 @@
Keep, // Keep content on the current line.
Break, // Partial content is on the current line.
Push, // Content is pushed to the next line.
- RevertToLastWrapOpportunity // The current content overflows and can't get wrapped. The content needs to be reverted back to the last wrapping opportunity.
+ // The current content overflows and can't get broken up into smaller bits.
+ RevertToLastWrapOpportunity, // The content needs to be reverted back to the last wrap opportunity.
+ RevertToLastNonOverflowingWrapOpportunity // The content needs to be reverted back to a wrap opportunity that still fits the line.
};
struct PartialTrailingContent {
size_t trailingRunIndex { 0 };
@@ -107,8 +109,9 @@
struct LineStatus {
InlineLayoutUnit availableWidth { 0 };
InlineLayoutUnit collapsibleWidth { 0 };
- bool lineHasFullyCollapsibleTrailingRun { false };
- bool lineIsEmpty { true };
+ Optional<InlineLayoutUnit> trailingSoftHyphenWidth;
+ bool hasFullyCollapsibleTrailingRun { false };
+ bool isEmpty { true };
};
Result processInlineContent(const ContinuousContent&, const LineStatus&);
Modified: trunk/Source/WebCore/layout/inlineformatting/InlineLine.cpp (267923 => 267924)
--- trunk/Source/WebCore/layout/inlineformatting/InlineLine.cpp 2020-10-03 15:02:22 UTC (rev 267923)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineLine.cpp 2020-10-03 15:12:17 UTC (rev 267924)
@@ -61,6 +61,7 @@
m_contentLogicalWidth = { };
m_isVisuallyEmpty = true;
m_runs.clear();
+ m_trailingSoftHyphenWidth = { };
m_trimmableTrailingContent.reset();
m_lineIsVisuallyEmptyBeforeTrimmableTrailingContent = { };
}
@@ -238,7 +239,7 @@
else
ASSERT_NOT_REACHED();
- // Check if this freshly appended content makes the line visually non-empty.
+ // Check if this newly appended content makes the line visually non-empty.
if (m_isVisuallyEmpty && !m_runs.isEmpty() && isRunVisuallyNonEmpty(m_runs.last()))
m_isVisuallyEmpty = false;
}
@@ -271,6 +272,7 @@
void Line::appendTextContent(const InlineTextItem& inlineTextItem, InlineLayoutUnit logicalWidth, bool needsHyphen)
{
+ auto& style = inlineTextItem.style();
auto willCollapseCompletely = [&] {
if (!inlineTextItem.isCollapsible())
return false;
@@ -290,7 +292,7 @@
ASSERT(run.isContainerStart() || run.isContainerEnd() || run.isWordBreakOpportunity());
}
// Leading whitespace.
- return !isWhitespacePreserved(inlineTextItem.style());
+ return !isWhitespacePreserved(style);
};
if (willCollapseCompletely())
@@ -312,7 +314,7 @@
m_runs.append({ inlineTextItem, contentLogicalWidth(), logicalWidth, needsHyphen });
m_contentLogicalWidth += logicalWidth;
// Set the trailing trimmable content.
- if (inlineTextItem.isWhitespace() && !TextUtil::shouldPreserveTrailingWhitespace(inlineTextItem.style())) {
+ if (inlineTextItem.isWhitespace() && !TextUtil::shouldPreserveTrailingWhitespace(style)) {
m_trimmableTrailingContent.addFullyTrimmableContent(m_runs.size() - 1, logicalWidth);
// If we ever trim this content, we need to know if the line visibility state needs to be recomputed.
if (m_trimmableTrailingContent.isEmpty())
@@ -321,8 +323,10 @@
}
// Any non-whitespace, no-trimmable content resets the existing trimmable.
m_trimmableTrailingContent.reset();
- if (!formattingContext().layoutState().shouldIgnoreTrailingLetterSpacing() && !inlineTextItem.isWhitespace() && inlineTextItem.style().letterSpacing() > 0)
- m_trimmableTrailingContent.addPartiallyTrimmableContent(m_runs.size() - 1, inlineTextItem.style().letterSpacing());
+ if (!formattingContext().layoutState().shouldIgnoreTrailingLetterSpacing() && !inlineTextItem.isWhitespace() && style.letterSpacing() > 0)
+ m_trimmableTrailingContent.addPartiallyTrimmableContent(m_runs.size() - 1, style.letterSpacing());
+ if (inlineTextItem.hasTrailingSoftHyphen())
+ m_trailingSoftHyphenWidth = InlineLayoutUnit { style.fontCascade().width(TextRun { StringView { style.hyphenString() } }) };
}
void Line::appendNonReplacedInlineBox(const InlineItem& inlineItem, InlineLayoutUnit logicalWidth)
@@ -333,6 +337,7 @@
m_runs.append({ inlineItem, contentLogicalWidth() + horizontalMargin.start, logicalWidth });
m_contentLogicalWidth += logicalWidth + horizontalMargin.start + horizontalMargin.end;
m_trimmableTrailingContent.reset();
+ m_trailingSoftHyphenWidth = { };
}
void Line::appendReplacedInlineBox(const InlineItem& inlineItem, InlineLayoutUnit logicalWidth)
@@ -344,6 +349,7 @@
void Line::appendLineBreak(const InlineItem& inlineItem)
{
+ m_trailingSoftHyphenWidth = { };
if (inlineItem.isHardLineBreak())
return m_runs.append({ inlineItem, contentLogicalWidth(), 0_lu });
// Soft line breaks (preserved new line characters) require inline text boxes for compatibility reasons.
Modified: trunk/Source/WebCore/layout/inlineformatting/InlineLine.h (267923 => 267924)
--- trunk/Source/WebCore/layout/inlineformatting/InlineLine.h 2020-10-03 15:02:22 UTC (rev 267923)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineLine.h 2020-10-03 15:12:17 UTC (rev 267924)
@@ -56,6 +56,8 @@
InlineLayoutUnit trimmableTrailingWidth() const { return m_trimmableTrailingContent.width(); }
bool isTrailingRunFullyTrimmable() const { return m_trimmableTrailingContent.isTrailingRunFullyTrimmable(); }
+ Optional<InlineLayoutUnit> trailingSoftHyphenWidth() const { return m_trailingSoftHyphenWidth; }
+
void moveLogicalLeft(InlineLayoutUnit);
void moveLogicalRight(InlineLayoutUnit);
@@ -183,6 +185,7 @@
InlineLayoutUnit m_lineLogicalLeft { 0 };
InlineLayoutUnit m_horizontalConstraint { 0 };
InlineLayoutUnit m_contentLogicalWidth { 0 };
+ Optional<InlineLayoutUnit> m_trailingSoftHyphenWidth { 0 };
bool m_isVisuallyEmpty { true };
Optional<bool> m_lineIsVisuallyEmptyBeforeTrimmableTrailingContent;
};
Modified: trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp (267923 => 267924)
--- trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp 2020-10-03 15:02:22 UTC (rev 267923)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp 2020-10-03 15:12:17 UTC (rev 267924)
@@ -592,7 +592,7 @@
// Check if this new content fits.
auto availableWidth = m_line.availableWidth() - floatContent.intrusiveWidth();
auto isLineConsideredEmpty = m_line.isVisuallyEmpty() && !m_contentIsConstrainedByFloat;
- auto lineStatus = InlineContentBreaker::LineStatus { availableWidth, m_line.trimmableTrailingWidth(), m_line.isTrailingRunFullyTrimmable(), isLineConsideredEmpty };
+ auto lineStatus = InlineContentBreaker::LineStatus { availableWidth, m_line.trimmableTrailingWidth(), m_line.trailingSoftHyphenWidth(), m_line.isTrailingRunFullyTrimmable(), isLineConsideredEmpty };
auto result = inlineContentBreaker.processInlineContent(continuousInlineContent, lineStatus);
if (result.lastWrapOpportunityItem)
m_wrapOpportunityList.append(result.lastWrapOpportunityItem);
@@ -615,6 +615,11 @@
ASSERT(!m_wrapOpportunityList.isEmpty());
return { InlineContentBreaker::IsEndOfLine::Yes, { rebuildLine(layoutRange), true } };
}
+ if (result.action == InlineContentBreaker::Result::Action::RevertToLastNonOverflowingWrapOpportunity) {
+ ASSERT(result.isEndOfLine == InlineContentBreaker::IsEndOfLine::Yes);
+ ASSERT_NOT_IMPLEMENTED_YET();
+ return { InlineContentBreaker::IsEndOfLine::Yes, { } };
+ }
if (result.action == InlineContentBreaker::Result::Action::Break) {
ASSERT(result.isEndOfLine == InlineContentBreaker::IsEndOfLine::Yes);
// Commit the combination of full and partial content on the current line.