Title: [288055] trunk/Source/WebCore
Revision
288055
Author
[email protected]
Date
2022-01-15 05:36:49 -0800 (Sat, 15 Jan 2022)

Log Message

[LFC][IFC] imported/w3c/web-platform-tests/css/css-text/word-break/word-break-normal-ar-000.html fails with incorrect run position
https://bugs.webkit.org/show_bug.cgi?id=235011

Reviewed by Antti Koivisto.

IFC (inherited from simple line layout) uses this technique of measuring the content with the trailing whitespace
and then simply subtract the whitespace width. It enables us to keep pushing content to the line without re-measuring it each time.
(the "non-whitespace + whitespace" pattern is extremely common for IFC content).
However in some cases when the trailing whitespace is trimmed, subtracting the trimmed width from the
content width instead of measuring it produces a visually incorrect result.
This patch fixes the most obvious cases when the incorrect width turns into an offset for the RTL content.

* layout/formattingContexts/inline/InlineLine.cpp:
(WebCore::Layout::Line::TrimmableTrailingContent::remove):
(WebCore::Layout::Line::Run::removeTrailingLetterSpacing):
(WebCore::Layout::Line::Run::removeTrailingWhitespace):
* layout/formattingContexts/inline/InlineLine.h:
* layout/formattingContexts/inline/text/TextUtil.cpp:
(WebCore::Layout::TextUtil::width):
* layout/formattingContexts/inline/text/TextUtil.h:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (288054 => 288055)


--- trunk/Source/WebCore/ChangeLog	2022-01-15 13:32:05 UTC (rev 288054)
+++ trunk/Source/WebCore/ChangeLog	2022-01-15 13:36:49 UTC (rev 288055)
@@ -1,3 +1,26 @@
+2022-01-15  Alan Bujtas  <[email protected]>
+
+        [LFC][IFC] imported/w3c/web-platform-tests/css/css-text/word-break/word-break-normal-ar-000.html fails with incorrect run position
+        https://bugs.webkit.org/show_bug.cgi?id=235011
+
+        Reviewed by Antti Koivisto.
+
+        IFC (inherited from simple line layout) uses this technique of measuring the content with the trailing whitespace
+        and then simply subtract the whitespace width. It enables us to keep pushing content to the line without re-measuring it each time.
+        (the "non-whitespace + whitespace" pattern is extremely common for IFC content).
+        However in some cases when the trailing whitespace is trimmed, subtracting the trimmed width from the
+        content width instead of measuring it produces a visually incorrect result.
+        This patch fixes the most obvious cases when the incorrect width turns into an offset for the RTL content.
+
+        * layout/formattingContexts/inline/InlineLine.cpp:
+        (WebCore::Layout::Line::TrimmableTrailingContent::remove):
+        (WebCore::Layout::Line::Run::removeTrailingLetterSpacing):
+        (WebCore::Layout::Line::Run::removeTrailingWhitespace):
+        * layout/formattingContexts/inline/InlineLine.h:
+        * layout/formattingContexts/inline/text/TextUtil.cpp:
+        (WebCore::Layout::TextUtil::width):
+        * layout/formattingContexts/inline/text/TextUtil.h:
+
 2022-01-14  Antoine Quint  <[email protected]>
 
         Setting `content: normal` on a ::marker should make computed style return resolved values

Modified: trunk/Source/WebCore/layout/formattingContexts/inline/InlineLine.cpp (288054 => 288055)


--- trunk/Source/WebCore/layout/formattingContexts/inline/InlineLine.cpp	2022-01-15 13:32:05 UTC (rev 288054)
+++ trunk/Source/WebCore/layout/formattingContexts/inline/InlineLine.cpp	2022-01-15 13:36:49 UTC (rev 288055)
@@ -343,7 +343,9 @@
                 m_hangingTrailingContent.add(inlineTextItem, logicalWidth);
         } else  {
             m_hangingTrailingContent.reset();
-            m_trimmableTrailingContent.addFullyTrimmableContent(lastRunIndex, contentLogicalWidth() - oldContentLogicalWidth);
+            auto trimmableWidth = logicalWidth;
+            auto trimmableContentOffset = (contentLogicalWidth() - oldContentLogicalWidth) - trimmableWidth;
+            m_trimmableTrailingContent.addFullyTrimmableContent(lastRunIndex, trimmableContentOffset, trimmableWidth);
         }
         m_trailingSoftHyphenWidth = { };
     } else {
@@ -450,11 +452,12 @@
 {
 }
 
-void Line::TrimmableTrailingContent::addFullyTrimmableContent(size_t runIndex, InlineLayoutUnit trimmableWidth)
+void Line::TrimmableTrailingContent::addFullyTrimmableContent(size_t runIndex, InlineLayoutUnit trimmableContentOffset, InlineLayoutUnit trimmableWidth)
 {
     // Any subsequent trimmable whitespace should collapse to zero advanced width and ignored at ::appendTextContent().
     ASSERT(!m_hasFullyTrimmableContent);
-    m_fullyTrimmableWidth = trimmableWidth;
+    m_fullyTrimmableWidth = trimmableContentOffset + trimmableWidth;
+    m_trimmableContentOffset = trimmableContentOffset;
     // Note that just because the trimmable width is 0 (font-size: 0px), it does not mean we don't have a trimmable trailing content.
     m_hasFullyTrimmableContent = true;
     m_firstTrimmableRunIndex = m_firstTrimmableRunIndex.value_or(runIndex);
@@ -481,12 +484,12 @@
     auto& trimmableRun = m_runs[*m_firstTrimmableRunIndex];
     ASSERT(trimmableRun.isText());
 
+    auto trimmedWidth = m_trimmableContentOffset;
     if (m_hasFullyTrimmableContent)
-        trimmableRun.removeTrailingWhitespace();
+        trimmedWidth += trimmableRun.removeTrailingWhitespace();
     if (m_partiallyTrimmableWidth)
-        trimmableRun.removeTrailingLetterSpacing();
+        trimmedWidth += trimmableRun.removeTrailingLetterSpacing();
 
-    auto trimmableWidth = width();
     // When the trimmable run is followed by some non-content runs, we need to adjust their horizontal positions.
     // e.g. <div>text is followed by trimmable content    <span> </span></div>
     // When the [text...] run is trimmed (trailing whitespace is removed), both "<span>" and "</span>" runs
@@ -495,7 +498,7 @@
     for (auto index = *m_firstTrimmableRunIndex + 1; index < m_runs.size(); ++index) {
         auto& run = m_runs[index];
         ASSERT(run.isWordBreakOpportunity() || run.isLineSpanningInlineBoxStart() || run.isInlineBoxStart() || run.isInlineBoxEnd() || run.isLineBreak());
-        run.moveHorizontally(-trimmableWidth);
+        run.moveHorizontally(-trimmedWidth);
     }
     if (!trimmableRun.textContent()->length) {
         // This trimmable run is fully collapsed now (e.g. <div><img>    <span></span></div>).
@@ -503,7 +506,7 @@
         m_runs.remove(*m_firstTrimmableRunIndex);
     }
     reset();
-    return trimmableWidth;
+    return trimmedWidth;
 }
 
 InlineLayoutUnit Line::TrimmableTrailingContent::removePartiallyTrimmableContent()
@@ -625,6 +628,7 @@
     if (!whitespaceType) {
         m_trailingWhitespace = { };
         m_textContent->length += inlineTextItem.length();
+        m_lastNonWhitespaceContentStart = inlineTextItem.start();
         return;
     }
     auto whitespaceWidth = !m_trailingWhitespace ? logicalWidth : m_trailingWhitespace->width + logicalWidth;
@@ -644,23 +648,39 @@
     return InlineLayoutUnit { letterSpacing() };
 }
 
-void Line::Run::removeTrailingLetterSpacing()
+InlineLayoutUnit Line::Run::removeTrailingLetterSpacing()
 {
     ASSERT(hasTrailingLetterSpacing());
-    shrinkHorizontally(trailingLetterSpacing());
+    auto trailingWidth = trailingLetterSpacing();
+    shrinkHorizontally(trailingWidth);
     ASSERT(logicalWidth() > 0 || (!logicalWidth() && letterSpacing() >= static_cast<float>(intMaxForLayoutUnit)));
+    return trailingWidth;
 }
 
-void Line::Run::removeTrailingWhitespace()
+InlineLayoutUnit Line::Run::removeTrailingWhitespace()
 {
     ASSERT(m_trailingWhitespace);
     // According to https://www.w3.org/TR/css-text-3/#white-space-property matrix
     // Trimmable whitespace is always collapsible so the length of the trailing trimmable whitespace is always 1 (or non-existent).
-    ASSERT(m_textContent->length);
+    ASSERT(m_textContent && m_textContent->length);
     constexpr size_t trailingTrimmableContentLength = 1;
+
+    auto trimmedWidth = m_trailingWhitespace->width;
+    if (m_lastNonWhitespaceContentStart && inlineDirection() == TextDirection::RTL) {
+        // While LTR content could also suffer from slightly incorrect content width after trimming trailing whitespace (see TextUtil::width)
+        // it hardly produces visually observable result.
+        // FIXME: This may still incorrectly leave some content on the line (vs. re-measuring also at ::expand).
+        auto& inlineTextBox = downcast<InlineTextBox>(*m_layoutBox);
+        auto startPosition = *m_lastNonWhitespaceContentStart;
+        auto endPosition = m_textContent->start + m_textContent->length;
+        RELEASE_ASSERT(startPosition < endPosition - trailingTrimmableContentLength);
+        if (inlineTextBox.content()[endPosition - 1] == space)
+            trimmedWidth = TextUtil::trailingWhitespaceWidth(inlineTextBox, m_style.fontCascade(), startPosition, endPosition);
+    }
     m_textContent->length -= trailingTrimmableContentLength;
-    shrinkHorizontally(m_trailingWhitespace->width);
     m_trailingWhitespace = { };
+    shrinkHorizontally(trimmedWidth);
+    return trimmedWidth;
 }
 
 }

Modified: trunk/Source/WebCore/layout/formattingContexts/inline/InlineLine.h (288054 => 288055)


--- trunk/Source/WebCore/layout/formattingContexts/inline/InlineLine.h	2022-01-15 13:32:05 UTC (rev 288054)
+++ trunk/Source/WebCore/layout/formattingContexts/inline/InlineLine.h	2022-01-15 13:36:49 UTC (rev 288055)
@@ -143,11 +143,11 @@
         bool hasCollapsibleTrailingWhitespace() const { return m_trailingWhitespace && (m_trailingWhitespace->type == TrailingWhitespace::Type::Collapsible || hasCollapsedTrailingWhitespace()); }
         bool hasCollapsedTrailingWhitespace() const { return m_trailingWhitespace && m_trailingWhitespace->type == TrailingWhitespace::Type::Collapsed; }
         static std::optional<TrailingWhitespace::Type> trailingWhitespaceType(const InlineTextItem&);
-        void removeTrailingWhitespace();
+        InlineLayoutUnit removeTrailingWhitespace();
 
         bool hasTrailingLetterSpacing() const;
         InlineLayoutUnit trailingLetterSpacing() const;
-        void removeTrailingLetterSpacing();
+        InlineLayoutUnit removeTrailingLetterSpacing();
 
         Type m_type { Type::Text };
         const Box* m_layoutBox { nullptr };
@@ -157,6 +157,7 @@
         InlineDisplay::Box::Expansion m_expansion;
         UBiDiLevel m_bidiLevel { UBIDI_DEFAULT_LTR };
         std::optional<TrailingWhitespace> m_trailingWhitespace { };
+        std::optional<size_t> m_lastNonWhitespaceContentStart { };
         std::optional<Text> m_textContent;
     };
     using RunList = Vector<Run, 10>;
@@ -185,7 +186,7 @@
     struct TrimmableTrailingContent {
         TrimmableTrailingContent(RunList&);
 
-        void addFullyTrimmableContent(size_t runIndex, InlineLayoutUnit trimmableWidth);
+        void addFullyTrimmableContent(size_t runIndex, InlineLayoutUnit trimmableContentOffset, InlineLayoutUnit trimmableWidth);
         void addPartiallyTrimmableContent(size_t runIndex, InlineLayoutUnit trimmableWidth);
         InlineLayoutUnit remove();
         InlineLayoutUnit removePartiallyTrimmableContent();
@@ -201,6 +202,7 @@
         RunList& m_runs;
         std::optional<size_t> m_firstTrimmableRunIndex;
         bool m_hasFullyTrimmableContent { false };
+        InlineLayoutUnit m_trimmableContentOffset { 0 };
         InlineLayoutUnit m_fullyTrimmableWidth { 0 };
         InlineLayoutUnit m_partiallyTrimmableWidth { 0 };
     };
@@ -245,6 +247,7 @@
     m_firstTrimmableRunIndex = { };
     m_fullyTrimmableWidth = { };
     m_partiallyTrimmableWidth = { };
+    m_trimmableContentOffset = { };
 }
 
 inline void Line::HangingTrailingContent::reset()

Modified: trunk/Source/WebCore/layout/formattingContexts/inline/text/TextUtil.cpp (288054 => 288055)


--- trunk/Source/WebCore/layout/formattingContexts/inline/text/TextUtil.cpp	2022-01-15 13:32:05 UTC (rev 288054)
+++ trunk/Source/WebCore/layout/formattingContexts/inline/text/TextUtil.cpp	2022-01-15 13:36:49 UTC (rev 288055)
@@ -57,7 +57,7 @@
     return TextUtil::width(inlineTextItem.inlineTextBox(), fontCascade, from, to, contentLogicalLeft);
 }
 
-InlineLayoutUnit TextUtil::width(const InlineTextBox& inlineTextBox, const FontCascade& fontCascade, unsigned from, unsigned to, InlineLayoutUnit contentLogicalLeft)
+InlineLayoutUnit TextUtil::width(const InlineTextBox& inlineTextBox, const FontCascade& fontCascade, unsigned from, unsigned to, InlineLayoutUnit contentLogicalLeft, UseTrailingWhitespaceMeasuringOptimization useTrailingWhitespaceMeasuringOptimization)
 {
     if (from == to)
         return 0;
@@ -65,8 +65,11 @@
     auto text = inlineTextBox.content();
     ASSERT(to <= text.length());
     auto hasKerningOrLigatures = fontCascade.enableKerning() || fontCascade.requiresShaping();
-    auto measureWithEndSpace = hasKerningOrLigatures && to < text.length() && text[to] == ' ';
-    if (measureWithEndSpace)
+    // The "non-whitespace" + "whitespace" pattern is very common for inline content and since most of the "non-whitespace" runs end up with
+    // their "whitespace" pair on the line (notable exception is when trailing whitespace is trimmed).
+    // Including the trailing whitespace here enables us to cut the number of text measures when placing content on the line.
+    auto extendedMeasuring = useTrailingWhitespaceMeasuringOptimization == UseTrailingWhitespaceMeasuringOptimization::Yes && hasKerningOrLigatures && to < text.length() && text[to] == space;
+    if (extendedMeasuring)
         ++to;
     float width = 0;
     if (inlineTextBox.canUseSimplifiedContentMeasuring())
@@ -79,12 +82,21 @@
         width = fontCascade.width(run);
     }
 
-    if (measureWithEndSpace)
+    if (extendedMeasuring)
         width -= (fontCascade.spaceWidth() + fontCascade.wordSpacing());
 
     return std::isnan(width) ? 0.0f : std::isinf(width) ? maxInlineLayoutUnit() : width;
 }
 
+InlineLayoutUnit TextUtil::trailingWhitespaceWidth(const InlineTextBox& inlineTextBox, const FontCascade& fontCascade, size_t startPosition, size_t endPosition)
+{
+    auto text = inlineTextBox.content();
+    ASSERT(endPosition > startPosition + 1);
+    ASSERT(text[endPosition - 1] == space);
+    return width(inlineTextBox, fontCascade, startPosition, endPosition, { }, TextUtil::UseTrailingWhitespaceMeasuringOptimization::Yes) - 
+        width(inlineTextBox, fontCascade, startPosition, endPosition - 1, { }, TextUtil::UseTrailingWhitespaceMeasuringOptimization::No);
+}
+
 template <typename TextIterator>
 static void fallbackFontsForRunWithIterator(HashSet<const Font*>& fallbackFonts, const FontCascade& fontCascade, const TextRun& run, TextIterator& textIterator)
 {

Modified: trunk/Source/WebCore/layout/formattingContexts/inline/text/TextUtil.h (288054 => 288055)


--- trunk/Source/WebCore/layout/formattingContexts/inline/text/TextUtil.h	2022-01-15 13:32:05 UTC (rev 288054)
+++ trunk/Source/WebCore/layout/formattingContexts/inline/text/TextUtil.h	2022-01-15 13:36:49 UTC (rev 288055)
@@ -46,8 +46,12 @@
 public:
     static InlineLayoutUnit width(const InlineTextItem&, const FontCascade&, InlineLayoutUnit contentLogicalLeft);
     static InlineLayoutUnit width(const InlineTextItem&, const FontCascade&, unsigned from, unsigned to, InlineLayoutUnit contentLogicalLeft);
-    static InlineLayoutUnit width(const InlineTextBox&, const FontCascade&, unsigned from, unsigned to, InlineLayoutUnit contentLogicalLeft);
 
+    enum class UseTrailingWhitespaceMeasuringOptimization : uint8_t { Yes, No };
+    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);
+
     using FallbackFontList = HashSet<const Font*>;
     static FallbackFontList fallbackFontsForRun(const Line::Run&, const RenderStyle&);
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to