[LFC][IFC] Trim trailing letter-spacing at inline container boundary
https://bugs.webkit.org/show_bug.cgi?id=204895
<rdar://problem/57666898>
Reviewed by Antti Koivisto.
According to https://www.w3.org/TR/css-text-3/#letter-spacing-property, "An inline box only
includes letter spacing between characters completely contained within that element".
This patch enables this behavior by trimming the trailing letter spacing at [container end].
<div>1<span style="letter-spacing: 100px;">2</span>3</div> ->
[1][container start][2][container end][3]
vs.
[1][container start][2<-----100px----->][container end][3]
* layout/inlineformatting/InlineLineBuilder.cpp:
(WebCore::Layout::LineBuilder::removeTrailingTrimmableContent):
(WebCore::Layout::LineBuilder::appendInlineContainerEnd):
(WebCore::Layout::LineBuilder::TrimmableContent::trim):
(WebCore::Layout::LineBuilder::TrimmableContent::trimTrailingRun):
* layout/inlineformatting/InlineLineBuilder.h:
(WebCore::Layout::LineBuilder::TrimmableContent::isTrailingRunPartiallyTrimmable const):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@253154 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 2638d77..08d0f00 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,28 @@
+2019-12-05 Zalan Bujtas <zalan@apple.com>
+
+ [LFC][IFC] Trim trailing letter-spacing at inline container boundary
+ https://bugs.webkit.org/show_bug.cgi?id=204895
+ <rdar://problem/57666898>
+
+ Reviewed by Antti Koivisto.
+
+ According to https://www.w3.org/TR/css-text-3/#letter-spacing-property, "An inline box only
+ includes letter spacing between characters completely contained within that element".
+ This patch enables this behavior by trimming the trailing letter spacing at [container end].
+
+ <div>1<span style="letter-spacing: 100px;">2</span>3</div> ->
+ [1][container start][2][container end][3]
+ vs.
+ [1][container start][2<-----100px----->][container end][3]
+
+ * layout/inlineformatting/InlineLineBuilder.cpp:
+ (WebCore::Layout::LineBuilder::removeTrailingTrimmableContent):
+ (WebCore::Layout::LineBuilder::appendInlineContainerEnd):
+ (WebCore::Layout::LineBuilder::TrimmableContent::trim):
+ (WebCore::Layout::LineBuilder::TrimmableContent::trimTrailingRun):
+ * layout/inlineformatting/InlineLineBuilder.h:
+ (WebCore::Layout::LineBuilder::TrimmableContent::isTrailingRunPartiallyTrimmable const):
+
2019-12-05 youenn fablet <youenn@apple.com>
maplike should define a set method
diff --git a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp b/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp
index a08424a..6374d4f 100644
--- a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp
+++ b/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp
@@ -367,9 +367,7 @@
if (m_trimmableContent.isEmpty() || m_inlineItemRuns.isEmpty())
return;
- m_lineBox.shrinkHorizontally(m_trimmableContent.width());
-
- m_trimmableContent.trim();
+ m_lineBox.shrinkHorizontally(m_trimmableContent.trim());
// If we trimmed the first visible run on the line, we need to re-check the visibility status.
if (!m_lineIsVisuallyEmptyBeforeTrimmableContent)
return;
@@ -441,6 +439,14 @@
void LineBuilder::appendInlineContainerEnd(const InlineItem& inlineItem, LayoutUnit logicalWidth)
{
// This is really just a placeholder to mark the end of the inline level container </span>.
+ auto trimTrailingLetterSpacing = [&] {
+ if (!m_trimmableContent.isTrailingRunPartiallyTrimmable())
+ return;
+ m_lineBox.shrinkHorizontally(m_trimmableContent.trimTrailingRun());
+ };
+ // Prevent trailing letter-spacing from spilling out of the inline container.
+ // https://drafts.csswg.org/css-text-3/#letter-spacing-property See example 21.
+ trimTrailingLetterSpacing();
appendNonBreakableSpace(inlineItem, contentLogicalRight(), logicalWidth);
}
@@ -712,10 +718,9 @@
m_firstRunIndex = m_firstRunIndex.valueOr(runIndex);
}
-void LineBuilder::TrimmableContent::trim()
+LayoutUnit LineBuilder::TrimmableContent::trim()
{
- if (!m_firstRunIndex)
- return;
+ ASSERT(!isEmpty());
#ifndef NDEBUG
auto hasSeenNonWhitespaceTextContent = false;
#endif
@@ -747,6 +752,38 @@
}
ASSERT(accumulatedTrimmedWidth == width());
reset();
+ return accumulatedTrimmedWidth;
+}
+
+LayoutUnit LineBuilder::TrimmableContent::trimTrailingRun()
+{
+ ASSERT(!isEmpty());
+ // Find the last trimmable run (it is not necessarily the last run e.g [container start][whitespace][container end])
+ for (auto index = m_inlineitemRunList.size(); index-- && *m_firstRunIndex >= index;) {
+ auto& run = m_inlineitemRunList[index];
+ if (!run.isText()) {
+ ASSERT(run.isContainerStart() || run.isContainerEnd());
+ continue;
+ }
+ LayoutUnit trimmedWidth;
+ if (run.isWhitespace()) {
+ trimmedWidth = run.logicalWidth();
+ run.setCollapsesToZeroAdvanceWidth();
+ } else {
+ ASSERT(run.hasTrailingLetterSpacing());
+ trimmedWidth = run.trailingLetterSpacing();
+ run.removeTrailingLetterSpacing();
+ }
+ m_width -= trimmedWidth;
+ // We managed to trim the last trimmable run.
+ if (index == *m_firstRunIndex) {
+ ASSERT(!m_width);
+ m_firstRunIndex = { };
+ }
+ return trimmedWidth;
+ }
+ ASSERT_NOT_REACHED();
+ return { };
}
LineBuilder::InlineItemRun::InlineItemRun(const InlineItem& inlineItem, LayoutUnit logicalLeft, LayoutUnit logicalWidth, WTF::Optional<Display::Run::TextContext> textContext)
diff --git a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h b/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h
index d9f77c5..1c01479 100644
--- a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h
+++ b/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h
@@ -205,13 +205,15 @@
TrimmableContent(InlineItemRunList&);
void append(size_t runIndex);
- void trim();
+ LayoutUnit trim();
+ LayoutUnit trimTrailingRun();
void reset();
LayoutUnit width() const { return m_width; }
Optional<size_t> firstRunIndex() { return m_firstRunIndex; }
bool isEmpty() const { return !m_firstRunIndex.hasValue(); }
bool isTrailingRunFullyTrimmable() const { return m_lastRunIsFullyTrimmable; }
+ bool isTrailingRunPartiallyTrimmable() const { return !isEmpty() && !isTrailingRunFullyTrimmable(); }
private:
InlineItemRunList& m_inlineitemRunList;