Title: [284188] trunk/Source/WebCore
Revision
284188
Author
[email protected]
Date
2021-10-14 11:52:28 -0700 (Thu, 14 Oct 2021)

Log Message

[LFC][IFC] tryBreakingTextRun should not take std::nullopt "available width" to indicate that the run is not overflowing
https://bugs.webkit.org/show_bug.cgi?id=231743

Reviewed by Antti Koivisto.

Let's not use magic value to indicate that the candidate run for breaking does not overflow the line (available width is nullopt -> infinite available width -> never overflow).

* layout/formattingContexts/inline/InlineContentBreaker.cpp:
(WebCore::Layout::InlineContentBreaker::tryBreakingTextRun const):
(WebCore::Layout::InlineContentBreaker::tryBreakingOverflowingRun const):
(WebCore::Layout::InlineContentBreaker::tryBreakingPreviousNonOverflowingRuns const):
(WebCore::Layout::InlineContentBreaker::tryBreakingNextOverflowingRuns const):
* layout/formattingContexts/inline/InlineContentBreaker.h:
* layout/integration/LayoutIntegrationCoverage.cpp:
(WebCore::LayoutIntegration::canUseForRenderInlineChild):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (284187 => 284188)


--- trunk/Source/WebCore/ChangeLog	2021-10-14 18:50:43 UTC (rev 284187)
+++ trunk/Source/WebCore/ChangeLog	2021-10-14 18:52:28 UTC (rev 284188)
@@ -1,3 +1,21 @@
+2021-10-14  Alan Bujtas  <[email protected]>
+
+        [LFC][IFC] tryBreakingTextRun should not take std::nullopt "available width" to indicate that the run is not overflowing
+        https://bugs.webkit.org/show_bug.cgi?id=231743
+
+        Reviewed by Antti Koivisto.
+
+        Let's not use magic value to indicate that the candidate run for breaking does not overflow the line (available width is nullopt -> infinite available width -> never overflow).
+
+        * layout/formattingContexts/inline/InlineContentBreaker.cpp:
+        (WebCore::Layout::InlineContentBreaker::tryBreakingTextRun const):
+        (WebCore::Layout::InlineContentBreaker::tryBreakingOverflowingRun const):
+        (WebCore::Layout::InlineContentBreaker::tryBreakingPreviousNonOverflowingRuns const):
+        (WebCore::Layout::InlineContentBreaker::tryBreakingNextOverflowingRuns const):
+        * layout/formattingContexts/inline/InlineContentBreaker.h:
+        * layout/integration/LayoutIntegrationCoverage.cpp:
+        (WebCore::LayoutIntegration::canUseForRenderInlineChild):
+
 2021-10-14  Antti Koivisto  <[email protected]>
 
         [CSS Cascade Layers] Layer should have higher priority than its descendant layers

Modified: trunk/Source/WebCore/layout/formattingContexts/inline/InlineContentBreaker.cpp (284187 => 284188)


--- trunk/Source/WebCore/layout/formattingContexts/inline/InlineContentBreaker.cpp	2021-10-14 18:50:43 UTC (rev 284187)
+++ trunk/Source/WebCore/layout/formattingContexts/inline/InlineContentBreaker.cpp	2021-10-14 18:52:28 UTC (rev 284188)
@@ -297,12 +297,149 @@
     return TextUtil::isWrappingAllowed(run.style);
 }
 
+struct CandidateTextRunForBreaking {
+    size_t index { 0 };
+    bool isOverflowingRun { true };
+    InlineLayoutUnit logicalLeft { 0 };
+};
+std::optional<InlineContentBreaker::PartialRun> InlineContentBreaker::tryBreakingTextRun(const ContinuousContent::RunList& runs, const CandidateTextRunForBreaking& candidateTextRun, InlineLayoutUnit availableWidth, bool lineHasWrapOpportunityAtPreviousPosition) const
+{
+    auto& candidateRun = runs[candidateTextRun.index];
+    ASSERT(candidateRun.inlineItem.isText());
+    auto& inlineTextItem = downcast<InlineTextItem>(candidateRun.inlineItem);
+    auto& style = candidateRun.style;
+    auto lineHasRoomForContent = availableWidth > 0;
+
+    auto breakRules = wordBreakBehavior(style, lineHasWrapOpportunityAtPreviousPosition);
+    if (breakRules.isEmpty())
+        return { };
+
+    auto& fontCascade = style.fontCascade();
+    if (breakRules.contains(WordBreakRule::AtArbitraryPositionWithinWords)) {
+        auto tryBreakingAtArbitraryPositionWithinWords = [&]() -> std::optional<PartialRun> {
+            // Breaking is allowed within “words”: specifically, in addition to soft wrap opportunities allowed for normal, any typographic letter units
+            // It does not affect rules governing the soft wrap opportunities created by white space. Hyphenation is not applied.
+            ASSERT(!breakRules.contains(WordBreakRule::AtHyphenationOpportunities));
+            if (inlineTextItem.isWhitespace()) {
+                // AtArbitraryPositionWithinWords does not affect the breaking opportunities around whitespace.
+                return { };
+            }
+
+            if (!inlineTextItem.length()) {
+                // Empty text runs may be breakable based on style, but in practice we can't really split them any further.
+                return PartialRun { };
+            }
+
+            if (!candidateTextRun.isOverflowingRun) {
+                // When the run can be split at arbitrary position we would normally just return the entire run when it is intended to fit on the line
+                // but here we have to check for mid-word break.
+                auto nextIndex = nextTextRunIndex(runs, candidateTextRun.index);
+                if (!nextIndex || !downcast<InlineTextItem>(runs[*nextIndex].inlineItem).isWhitespace())
+                    return PartialRun { inlineTextItem.length(), TextUtil::width(inlineTextItem, fontCascade, candidateTextRun.logicalLeft) };
+                // We've got room for the run but we have to try to break it mid-word.
+                if (inlineTextItem.length() == 1) {
+                    // We can't mid-word break a single character.
+                    return { };
+                }
+                auto startPosition = inlineTextItem.start();
+                auto endPosition = inlineTextItem.end() - 1;
+                return PartialRun { inlineTextItem.length() - 1, TextUtil::width(inlineTextItem, fontCascade, startPosition, endPosition, candidateTextRun.logicalLeft) };
+            }
+
+            if (!lineHasRoomForContent) {
+                auto previousIndex = previousTextRunIndex(runs, candidateTextRun.index);
+                if (!previousIndex || !downcast<InlineTextItem>(runs[*previousIndex].inlineItem).isWhitespace()) {
+                    // Fast path for cases when there's no room at all. We can break this content at the start.
+                    return PartialRun { };
+                }
+                if (inlineTextItem.length() == 1) {
+                    // We can't mid-word break a single character.
+                    return { };
+                }
+                auto startPosition = inlineTextItem.start() + 1;
+                auto endPosition = inlineTextItem.end();
+                return PartialRun { inlineTextItem.length() - 1, TextUtil::width(inlineTextItem, fontCascade, startPosition, endPosition, candidateTextRun.logicalLeft) };
+            }
+            auto midWordBreak = TextUtil::midWordBreak(inlineTextItem, fontCascade, candidateRun.logicalWidth, availableWidth, candidateTextRun.logicalLeft);
+            return PartialRun { midWordBreak.length, midWordBreak.logicalWidth };
+        };
+        return tryBreakingAtArbitraryPositionWithinWords();
+    }
+
+    if (breakRules.contains(WordBreakRule::AtHyphenationOpportunities)) {
+        auto tryBreakingAtHyphenationOpportunity = [&]() -> std::optional<PartialRun> {
+            // Find the hyphen position as follows:
+            // 1. Split the text by taking the hyphen width into account
+            // 2. Find the last hyphen position before the split position
+            if (candidateTextRun.isOverflowingRun && !lineHasRoomForContent) {
+                // We won't be able to find hyphen location when there's no available space.
+                return { };
+            }
+            auto runLength = inlineTextItem.length();
+            unsigned limitBefore = style.hyphenationLimitBefore() == RenderStyle::initialHyphenationLimitBefore() ? 0 : style.hyphenationLimitBefore();
+            unsigned limitAfter = style.hyphenationLimitAfter() == RenderStyle::initialHyphenationLimitAfter() ? 0 : style.hyphenationLimitAfter();
+            // Check if this run can accommodate the before/after limits at all before start measuring text.
+            if (limitBefore >= runLength || limitAfter >= runLength || limitBefore + limitAfter > runLength)
+                return { };
+
+            unsigned leftSideLength = runLength;
+            auto hyphenWidth = InlineLayoutUnit { fontCascade.width(TextRun { StringView { style.hyphenString() } }) };
+            if (candidateTextRun.isOverflowingRun) {
+                auto availableWidthExcludingHyphen = availableWidth - hyphenWidth;
+                if (availableWidthExcludingHyphen <= 0 || !enoughWidthForHyphenation(availableWidthExcludingHyphen, fontCascade.pixelSize()))
+                    return { };
+                leftSideLength = TextUtil::midWordBreak(inlineTextItem, fontCascade, candidateRun.logicalWidth, availableWidthExcludingHyphen, candidateTextRun.logicalLeft).length;
+            }
+            if (leftSideLength < limitBefore)
+                return { };
+            // Adjust before index to accommodate the limit-after value (it's the last potential hyphen location in this run).
+            auto hyphenBefore = std::min(leftSideLength, runLength - limitAfter) + 1;
+            unsigned hyphenLocation = lastHyphenLocation(StringView(inlineTextItem.inlineTextBox().content()).substring(inlineTextItem.start(), inlineTextItem.length()), hyphenBefore, style.computedLocale());
+            if (!hyphenLocation || hyphenLocation < limitBefore)
+                return { };
+            // hyphenLocation is relative to the start of this InlineItemText.
+            ASSERT(inlineTextItem.start() + hyphenLocation < inlineTextItem.end());
+            auto trailingPartialRunWidthWithHyphen = TextUtil::width(inlineTextItem, fontCascade, inlineTextItem.start(), inlineTextItem.start() + hyphenLocation, candidateTextRun.logicalLeft);
+            return PartialRun { hyphenLocation, trailingPartialRunWidthWithHyphen, hyphenWidth };
+        };
+        if (auto partialRun = tryBreakingAtHyphenationOpportunity())
+            return partialRun;
+    }
+
+    if (breakRules.contains(WordBreakRule::AtArbitraryPosition)) {
+        auto tryBreakingAtArbitraryPosition = [&]() -> PartialRun {
+            if (!inlineTextItem.length()) {
+                // Empty text runs may be breakable based on style, but in practice we can't really split them any further.
+                return { };
+            }
+            if (!candidateTextRun.isOverflowingRun) {
+                // When the run can be split at arbitrary position let's just return the entire run when it is intended to fit on the line.
+                ASSERT(inlineTextItem.length());
+                auto trailingPartialRunWidth = TextUtil::width(inlineTextItem, fontCascade, candidateTextRun.logicalLeft);
+                return { inlineTextItem.length(), trailingPartialRunWidth };
+            }
+            if (!lineHasRoomForContent) {
+                // Fast path for cases when there's no room at all. The content is breakable but we don't have space for it.
+                return { };
+            }
+            auto midWordBreak = TextUtil::midWordBreak(inlineTextItem, fontCascade, candidateRun.logicalWidth, availableWidth, candidateTextRun.logicalLeft);
+            return { midWordBreak.length, midWordBreak.logicalWidth };
+        };
+        // With arbitrary breaking there's always a valid breaking position (even if it is before the first position).
+        return tryBreakingAtArbitraryPosition();
+    }
+
+    return { };
+}
+
 std::optional<InlineContentBreaker::OverflowingTextContent::BreakingPosition> InlineContentBreaker::tryBreakingOverflowingRun(const LineStatus& lineStatus, const ContinuousContent::RunList& runs, size_t overflowingRunIndex, InlineLayoutUnit nonOverflowingContentWidth) const
 {
     auto overflowingRun = runs[overflowingRunIndex];
     if (!isWrappableRun(overflowingRun))
         return { };
-    auto partialOverflowingRun = tryBreakingTextRun(runs, overflowingRunIndex, lineStatus.contentLogicalRight + nonOverflowingContentWidth, std::max(0.0f, lineStatus.availableWidth - nonOverflowingContentWidth), lineStatus.hasWrapOpportunityAtPreviousPosition);
+
+    auto avilableWidth = std::max(0.0f, lineStatus.availableWidth - nonOverflowingContentWidth);
+    auto partialOverflowingRun = tryBreakingTextRun(runs, { overflowingRunIndex, true, lineStatus.contentLogicalRight + nonOverflowingContentWidth }, avilableWidth, lineStatus.hasWrapOpportunityAtPreviousPosition);
     if (!partialOverflowingRun)
         return { };
     if (partialOverflowingRun->length)
@@ -324,7 +461,8 @@
         if (!isWrappableRun(run))
             continue;
         ASSERT(run.inlineItem.isText());
-        if (auto partialRun = tryBreakingTextRun(runs, index, lineStatus.contentLogicalRight + previousContentWidth, { }, lineStatus.hasWrapOpportunityAtPreviousPosition)) {
+        auto avilableWidth = std::max(0.0f, lineStatus.availableWidth - previousContentWidth);
+        if (auto partialRun = tryBreakingTextRun(runs, { index, false, lineStatus.contentLogicalRight + previousContentWidth }, avilableWidth, lineStatus.hasWrapOpportunityAtPreviousPosition)) {
             // We know this run fits, so if breaking is allowed on the run, it should return a non-empty left-side
             // since it's either at hyphen position or the entire run is returned.
             ASSERT(partialRun->length);
@@ -346,7 +484,7 @@
         }
         ASSERT(run.inlineItem.isText());
         // At this point the available space is zero. Let's try the break these overflowing set of runs at the earliest possible.
-        if (auto partialRun = tryBreakingTextRun(runs, index, lineStatus.contentLogicalRight + nextContentWidth, 0, lineStatus.hasWrapOpportunityAtPreviousPosition)) {
+        if (auto partialRun = tryBreakingTextRun(runs, { index, true, lineStatus.contentLogicalRight + nextContentWidth }, 0, lineStatus.hasWrapOpportunityAtPreviousPosition)) {
             // <span>unbreakable_and_overflows<span style="word-break: break-all">breakable</span>
             // The partial run length could very well be 0 meaning the trailing run is actually the overflowing run (see above in the example).
             if (partialRun->length) {
@@ -441,133 +579,6 @@
     return includeHyphenationIfAllowed({ });
 }
 
-std::optional<InlineContentBreaker::PartialRun> InlineContentBreaker::tryBreakingTextRun(const ContinuousContent::RunList& runs, size_t candidateRunIndex, InlineLayoutUnit logicalLeft, std::optional<InlineLayoutUnit> availableWidth, bool hasWrapOpportunityAtPreviousPosition) const
-{
-    auto& candidateRun = runs[candidateRunIndex];
-    ASSERT(candidateRun.inlineItem.isText());
-    auto& inlineTextItem = downcast<InlineTextItem>(candidateRun.inlineItem);
-    auto& style = candidateRun.style;
-    auto availableSpaceIsInfinite = !availableWidth.has_value();
-
-    auto breakRules = wordBreakBehavior(style, hasWrapOpportunityAtPreviousPosition);
-    if (breakRules.isEmpty())
-        return { };
-
-    auto& fontCascade = style.fontCascade();
-    if (breakRules.contains(WordBreakRule::AtArbitraryPositionWithinWords)) {
-        // Breaking is allowed within “words”: specifically, in addition to soft wrap opportunities allowed for normal, any typographic letter units
-        // It does not affect rules governing the soft wrap opportunities created by white space. Hyphenation is not applied.
-        ASSERT(!breakRules.contains(WordBreakRule::AtHyphenationOpportunities));
-        if (inlineTextItem.isWhitespace()) {
-            // AtArbitraryPositionWithinWords does not affect the breaking opportunities around whitespace.
-            return { };
-        }
-
-        if (!inlineTextItem.length()) {
-            // Empty text runs may be breakable based on style, but in practice we can't really split them any further.
-            return PartialRun { };
-        }
-
-        if (availableSpaceIsInfinite) {
-            // When the run can be split at arbitrary position we would normally just return the entire run when it is intended to fit on the line
-            // but here we have to check for mid-word break.
-            auto nextIndex = nextTextRunIndex(runs, candidateRunIndex);
-            if (!nextIndex || !downcast<InlineTextItem>(runs[*nextIndex].inlineItem).isWhitespace())
-                return PartialRun { inlineTextItem.length(), TextUtil::width(inlineTextItem, fontCascade, logicalLeft) };
-            // We've got room for the run but we have to try to break it mid-word.
-            if (inlineTextItem.length() == 1) {
-                // We can't mid-word break a single character.
-                return { };
-            }
-            auto startPosition = inlineTextItem.start();
-            auto endPosition = inlineTextItem.end() - 1;
-            return PartialRun { inlineTextItem.length() - 1, TextUtil::width(inlineTextItem, fontCascade, startPosition, endPosition, logicalLeft) };
-        }
-
-        if (!*availableWidth) {
-            auto previousIndex = previousTextRunIndex(runs, candidateRunIndex);
-            if (!previousIndex || !downcast<InlineTextItem>(runs[*previousIndex].inlineItem).isWhitespace()) {
-                // Fast path for cases when there's no room at all. We can break this content at the start.
-                return PartialRun { };
-            }
-            if (inlineTextItem.length() == 1) {
-                // We can't mid-word break a single character.
-                return { };
-            }
-            auto startPosition = inlineTextItem.start() + 1;
-            auto endPosition = inlineTextItem.end();
-            return PartialRun { inlineTextItem.length() - 1, TextUtil::width(inlineTextItem, fontCascade, startPosition, endPosition, logicalLeft) };
-        }
-        auto midWordBreak = TextUtil::midWordBreak(inlineTextItem, fontCascade, candidateRun.logicalWidth, *availableWidth, logicalLeft);
-        return PartialRun { midWordBreak.length, midWordBreak.logicalWidth };
-    }
-
-    if (breakRules.contains(WordBreakRule::AtHyphenationOpportunities)) {
-        auto tryBreakingAtHyphenationOpportunity = [&]() -> std::optional<PartialRun> {
-            // Find the hyphen position as follows:
-            // 1. Split the text by taking the hyphen width into account
-            // 2. Find the last hyphen position before the split position
-            if (!availableSpaceIsInfinite && !*availableWidth) {
-                // We won't be able to find hyphen location when there's no available space.
-                return { };
-            }
-            auto runLength = inlineTextItem.length();
-            unsigned limitBefore = style.hyphenationLimitBefore() == RenderStyle::initialHyphenationLimitBefore() ? 0 : style.hyphenationLimitBefore();
-            unsigned limitAfter = style.hyphenationLimitAfter() == RenderStyle::initialHyphenationLimitAfter() ? 0 : style.hyphenationLimitAfter();
-            // Check if this run can accommodate the before/after limits at all before start measuring text.
-            if (limitBefore >= runLength || limitAfter >= runLength || limitBefore + limitAfter > runLength)
-                return { };
-
-            unsigned leftSideLength = runLength;
-            auto hyphenWidth = InlineLayoutUnit { fontCascade.width(TextRun { StringView { style.hyphenString() } }) };
-            if (!availableSpaceIsInfinite) {
-                auto availableWidthExcludingHyphen = *availableWidth - hyphenWidth;
-                if (availableWidthExcludingHyphen <= 0 || !enoughWidthForHyphenation(availableWidthExcludingHyphen, fontCascade.pixelSize()))
-                    return { };
-                leftSideLength = TextUtil::midWordBreak(inlineTextItem, fontCascade, candidateRun.logicalWidth, availableWidthExcludingHyphen, logicalLeft).length;
-            }
-            if (leftSideLength < limitBefore)
-                return { };
-            // Adjust before index to accommodate the limit-after value (it's the last potential hyphen location in this run).
-            auto hyphenBefore = std::min(leftSideLength, runLength - limitAfter) + 1;
-            unsigned hyphenLocation = lastHyphenLocation(StringView(inlineTextItem.inlineTextBox().content()).substring(inlineTextItem.start(), inlineTextItem.length()), hyphenBefore, style.computedLocale());
-            if (!hyphenLocation || hyphenLocation < limitBefore)
-                return { };
-            // hyphenLocation is relative to the start of this InlineItemText.
-            ASSERT(inlineTextItem.start() + hyphenLocation < inlineTextItem.end());
-            auto trailingPartialRunWidthWithHyphen = TextUtil::width(inlineTextItem, fontCascade, inlineTextItem.start(), inlineTextItem.start() + hyphenLocation, logicalLeft);
-            return PartialRun { hyphenLocation, trailingPartialRunWidthWithHyphen, hyphenWidth };
-        };
-        if (auto partialRun = tryBreakingAtHyphenationOpportunity())
-            return partialRun;
-    }
-
-    if (breakRules.contains(WordBreakRule::AtArbitraryPosition)) {
-        auto tryBreakingAtArbitraryPosition = [&]() -> PartialRun {
-            if (!inlineTextItem.length()) {
-                // Empty text runs may be breakable based on style, but in practice we can't really split them any further.
-                return { };
-            }
-            if (availableSpaceIsInfinite) {
-                // When the run can be split at arbitrary position let's just return the entire run when it is intended to fit on the line.
-                ASSERT(inlineTextItem.length());
-                auto trailingPartialRunWidth = TextUtil::width(inlineTextItem, fontCascade, logicalLeft);
-                return { inlineTextItem.length(), trailingPartialRunWidth };
-            }
-            if (!*availableWidth) {
-                // Fast path for cases when there's no room at all. The content is breakable but we don't have space for it.
-                return { };
-            }
-            auto midWordBreak = TextUtil::midWordBreak(inlineTextItem, fontCascade, candidateRun.logicalWidth, *availableWidth, logicalLeft);
-            return { midWordBreak.length, midWordBreak.logicalWidth };
-        };
-        // With arbitrary breaking there's always a valid breaking position (even if it is before the first position).
-        return tryBreakingAtArbitraryPosition();
-    }
-
-    return { };
-}
-
 void InlineContentBreaker::ContinuousContent::append(const InlineItem& inlineItem, const RenderStyle& style, InlineLayoutUnit logicalWidth, std::optional<InlineLayoutUnit> collapsibleWidth)
 {
     ASSERT(inlineItem.isText() || inlineItem.isBox() || inlineItem.isInlineBoxStart() || inlineItem.isInlineBoxEnd());

Modified: trunk/Source/WebCore/layout/formattingContexts/inline/InlineContentBreaker.h (284187 => 284188)


--- trunk/Source/WebCore/layout/formattingContexts/inline/InlineContentBreaker.h	2021-10-14 18:50:43 UTC (rev 284187)
+++ trunk/Source/WebCore/layout/formattingContexts/inline/InlineContentBreaker.h	2021-10-14 18:52:28 UTC (rev 284188)
@@ -34,6 +34,7 @@
 namespace Layout {
 
 class InlineItem;
+struct CandidateTextRunForBreaking;
 
 class InlineContentBreaker {
 public:
@@ -134,7 +135,7 @@
         std::optional<BreakingPosition> breakingPosition { }; // Where we actually break this overflowing content.
     };
     OverflowingTextContent processOverflowingContentWithText(const ContinuousContent&, const LineStatus&) const;
-    std::optional<PartialRun> tryBreakingTextRun(const ContinuousContent::RunList& runs, size_t candidateRunIndex, InlineLayoutUnit logicalLeft, std::optional<InlineLayoutUnit> availableWidth, bool hasWrapOpportunityAtPreviousPosition) const;
+    std::optional<PartialRun> tryBreakingTextRun(const ContinuousContent::RunList& runs, const CandidateTextRunForBreaking&, InlineLayoutUnit availableWidth, bool lineHasWrapOpportunityAtPreviousPosition) 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;
     std::optional<OverflowingTextContent::BreakingPosition> tryBreakingNextOverflowingRuns(const LineStatus&, const ContinuousContent::RunList&, size_t overflowingRunIndex, InlineLayoutUnit nonOverflowingContentWidth) const;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to