Title: [253934] trunk/Source/WebCore
Revision
253934
Author
[email protected]
Date
2019-12-29 07:59:01 -0800 (Sun, 29 Dec 2019)

Log Message

[LFC][IFC] Move soft wrap opportunity code out of LineBreaker
https://bugs.webkit.org/show_bug.cgi?id=205621
<rdar://problem/58227670>

Reviewed by Antti Koivisto.

Let's not mix soft and line wrap opportunity logic.

* layout/inlineformatting/InlineLineBreaker.cpp:
(WebCore::Layout::ContinousContent::runs const):
(WebCore::Layout::ContinousContent::isEmpty const):
(WebCore::Layout::ContinousContent::size const):
(WebCore::Layout::ContinousContent::width const):
(WebCore::Layout::ContinousContent::nonCollapsibleWidth const):
(WebCore::Layout::ContinousContent::hasTrailingCollapsibleContent const):
(WebCore::Layout::ContinousContent::isTrailingContentFullyCollapsible const):
(WebCore::Layout::LineBreaker::wrapTextContent const):
(WebCore::Layout::ContinousContent::ContinousContent):
(WebCore::Layout::ContinousContent::hasTextContentOnly const):
(WebCore::Layout::ContinousContent::isVisuallyEmptyWhitespaceContentOnly const):
(WebCore::Layout::ContinousContent::firstTextRunIndex const):
(WebCore::Layout::ContinousContent::lastContentRunIndex const):
(WebCore::Layout::ContinousContent::hasNonContentRunsOnly const):
(WebCore::Layout::ContinousContent::lastWrapOpportunityIndex const):
(WebCore::Layout::ContinousContent::TrailingCollapsibleContent::reset):
(WebCore::Layout::endsWithSoftWrapOpportunity): Deleted.
(WebCore::Layout::isAtSoftWrapOpportunity): Deleted.
(WebCore::Layout::LineBreaker::nextWrapOpportunity): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::ContinousContent): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::hasTextContentOnly const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::isVisuallyEmptyWhitespaceContentOnly const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::firstTextRunIndex const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::lastContentRunIndex const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::hasNonContentRunsOnly const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::lastWrapOpportunityIndex const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::TrailingCollapsibleContent::reset): Deleted.
* layout/inlineformatting/InlineLineBreaker.h:
(WebCore::Layout::LineBreaker::ContinousContent::runs const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::isEmpty const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::size const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::width const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::nonCollapsibleWidth const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::hasTrailingCollapsibleContent const): Deleted.
(WebCore::Layout::LineBreaker::ContinousContent::isTrailingContentFullyCollapsible const): Deleted.
* layout/inlineformatting/LineLayoutContext.cpp:
(WebCore::Layout::endsWithSoftWrapOpportunity):
(WebCore::Layout::isAtSoftWrapOpportunity):
(WebCore::Layout::nextWrapOpportunity):
(WebCore::Layout::LineLayoutContext::nextContentForLine):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (253933 => 253934)


--- trunk/Source/WebCore/ChangeLog	2019-12-29 07:01:18 UTC (rev 253933)
+++ trunk/Source/WebCore/ChangeLog	2019-12-29 15:59:01 UTC (rev 253934)
@@ -1,3 +1,55 @@
+2019-12-29  Zalan Bujtas  <[email protected]>
+
+        [LFC][IFC] Move soft wrap opportunity code out of LineBreaker
+        https://bugs.webkit.org/show_bug.cgi?id=205621
+        <rdar://problem/58227670>
+
+        Reviewed by Antti Koivisto.
+
+        Let's not mix soft and line wrap opportunity logic.
+
+        * layout/inlineformatting/InlineLineBreaker.cpp:
+        (WebCore::Layout::ContinousContent::runs const):
+        (WebCore::Layout::ContinousContent::isEmpty const):
+        (WebCore::Layout::ContinousContent::size const):
+        (WebCore::Layout::ContinousContent::width const):
+        (WebCore::Layout::ContinousContent::nonCollapsibleWidth const):
+        (WebCore::Layout::ContinousContent::hasTrailingCollapsibleContent const):
+        (WebCore::Layout::ContinousContent::isTrailingContentFullyCollapsible const):
+        (WebCore::Layout::LineBreaker::wrapTextContent const):
+        (WebCore::Layout::ContinousContent::ContinousContent):
+        (WebCore::Layout::ContinousContent::hasTextContentOnly const):
+        (WebCore::Layout::ContinousContent::isVisuallyEmptyWhitespaceContentOnly const):
+        (WebCore::Layout::ContinousContent::firstTextRunIndex const):
+        (WebCore::Layout::ContinousContent::lastContentRunIndex const):
+        (WebCore::Layout::ContinousContent::hasNonContentRunsOnly const):
+        (WebCore::Layout::ContinousContent::lastWrapOpportunityIndex const):
+        (WebCore::Layout::ContinousContent::TrailingCollapsibleContent::reset):
+        (WebCore::Layout::endsWithSoftWrapOpportunity): Deleted.
+        (WebCore::Layout::isAtSoftWrapOpportunity): Deleted.
+        (WebCore::Layout::LineBreaker::nextWrapOpportunity): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::ContinousContent): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::hasTextContentOnly const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::isVisuallyEmptyWhitespaceContentOnly const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::firstTextRunIndex const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::lastContentRunIndex const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::hasNonContentRunsOnly const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::lastWrapOpportunityIndex const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::TrailingCollapsibleContent::reset): Deleted.
+        * layout/inlineformatting/InlineLineBreaker.h:
+        (WebCore::Layout::LineBreaker::ContinousContent::runs const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::isEmpty const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::size const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::width const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::nonCollapsibleWidth const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::hasTrailingCollapsibleContent const): Deleted.
+        (WebCore::Layout::LineBreaker::ContinousContent::isTrailingContentFullyCollapsible const): Deleted.
+        * layout/inlineformatting/LineLayoutContext.cpp:
+        (WebCore::Layout::endsWithSoftWrapOpportunity):
+        (WebCore::Layout::isAtSoftWrapOpportunity):
+        (WebCore::Layout::nextWrapOpportunity):
+        (WebCore::Layout::LineLayoutContext::nextContentForLine):
+
 2019-12-28  Zalan Bujtas  <[email protected]>
 
         [LFC][IFC] Keep track of last line wrap opportunity

Modified: trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp (253933 => 253934)


--- trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp	2019-12-29 07:01:18 UTC (rev 253933)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp	2019-12-29 15:59:01 UTC (rev 253934)
@@ -49,6 +49,43 @@
     return whitespace == WhiteSpace::Pre || whitespace == WhiteSpace::PreWrap || whitespace == WhiteSpace::BreakSpaces;
 }
 
+struct ContinousContent {
+    ContinousContent(const LineBreaker::RunList&);
+
+    const LineBreaker::RunList& runs() const { return m_runs; }
+    bool isEmpty() const { return m_runs.isEmpty(); }
+    bool hasTextContentOnly() const;
+    bool isVisuallyEmptyWhitespaceContentOnly() const;
+    bool hasNonContentRunsOnly() const;
+    size_t size() const { return m_runs.size(); }
+    InlineLayoutUnit width() const { return m_width; }
+    InlineLayoutUnit nonCollapsibleWidth() const { return m_width - m_trailingCollapsibleContent.width; }
+
+    bool hasTrailingCollapsibleContent() const { return !!m_trailingCollapsibleContent.width; }
+    bool isTrailingContentFullyCollapsible() const { return m_trailingCollapsibleContent.isFullyCollapsible; }
+    Optional<size_t> lastWrapOpportunityIndex() const;
+
+    Optional<size_t> firstTextRunIndex() const;
+    Optional<size_t> lastContentRunIndex() const;
+
+private:
+    LineBreaker::RunList m_runs;
+    struct TrailingCollapsibleContent {
+        void reset();
+
+        bool isFullyCollapsible { false };
+        InlineLayoutUnit width { 0 };
+    };
+    TrailingCollapsibleContent m_trailingCollapsibleContent;
+    InlineLayoutUnit m_width { 0 };
+};
+
+struct WrappedTextContent {
+    unsigned trailingRunIndex { 0 };
+    bool contentOverflows { false };
+    Optional<LineBreaker::PartialRun> partialTrailingRun;
+};
+
 bool LineBreaker::isContentWrappingAllowed(const ContinousContent& candidateRuns) const
 {
     // Use the last inline item with content (where we would be wrapping) to decide if content wrapping is allowed.
@@ -151,7 +188,7 @@
     return !lineIsEmpty && floatLogicalWidth > availableWidth;
 }
 
-Optional<LineBreaker::WrappedTextContent> LineBreaker::wrapTextContent(const RunList& runs, const LineStatus& lineStatus) const
+Optional<WrappedTextContent> LineBreaker::wrapTextContent(const RunList& runs, const LineStatus& lineStatus) const
 {
     auto isContentSplitAllowed = [] (auto& run) {
         ASSERT(run.inlineItem.isText() || run.inlineItem.isContainerStart() || run.inlineItem.isContainerEnd());
@@ -290,145 +327,7 @@
     return { };
 }
 
-static bool endsWithSoftWrapOpportunity(const InlineTextItem& currentTextItem, const InlineTextItem& nextInlineTextItem)
-{
-    ASSERT(!nextInlineTextItem.isWhitespace());
-    // We are at the position after a whitespace.
-    if (currentTextItem.isWhitespace())
-        return true;
-    // When both these non-whitespace runs belong to the same layout box, it's guaranteed that
-    // they are split at a soft breaking opportunity. See InlineTextItem::moveToNextBreakablePosition.
-    if (&currentTextItem.layoutBox() == &nextInlineTextItem.layoutBox())
-        return true;
-    // Now we need to collect at least 3 adjacent characters to be able to make a descision whether the previous text item ends with breaking opportunity.
-    // [ex-][ample] <- second to last[x] last[-] current[a]
-    // We need at least 1 character in the current inline text item and 2 more from previous inline items.
-    auto previousContent = currentTextItem.layoutBox().textContext()->content;
-    auto lineBreakIterator = LazyLineBreakIterator { nextInlineTextItem.layoutBox().textContext()->content };
-    auto previousContentLength = previousContent.length();
-    // FIXME: We should look into the entire uncommitted content for more text context.
-    UChar lastCharacter = previousContentLength ? previousContent[previousContentLength - 1] : 0;
-    UChar secondToLastCharacter = previousContentLength > 1 ? previousContent[previousContentLength - 2] : 0;
-    lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
-    // Now check if we can break right at the inline item boundary.
-    // With the [ex-ample], findNextBreakablePosition should return the startPosition (0).
-    // FIXME: Check if there's a more correct way of finding breaking opportunities.
-    return !TextUtil::findNextBreakablePosition(lineBreakIterator, 0, nextInlineTextItem.style());
-}
-
-static bool isAtSoftWrapOpportunity(const InlineItem& current, const InlineItem& next)
-{
-    // "is at" simple means that there's a soft wrap opportunity right after the [current].
-    // [text][ ][text][container start]... (<div>text content<span>..</div>)
-    // soft wrap indexes: 0 and 1 definitely, 2 depends on the content after the [container start].
-
-    // https://drafts.csswg.org/css-text-3/#line-break-details
-    // Figure out if the new incoming content puts the uncommitted content on a soft wrap opportunity.
-    // e.g. [container start][prior_continuous_content][container end] (<span>prior_continuous_content</span>)
-    // An incoming <img> box would enable us to commit the "<span>prior_continuous_content</span>" content
-    // but an incoming text content would not necessarily.
-    ASSERT(current.isText() || current.isBox());
-    ASSERT(next.isText() || next.isBox());
-    if (current.isBox() || next.isBox()) {
-        // [text][container start][container end][inline box] (text<span></span><img>) : there's a soft wrap opportunity between the [text] and [img].
-        // The line breaking behavior of a replaced element or other atomic inline is equivalent to an ideographic character.
-        return true;
-    }
-    if (current.style().lineBreak() == LineBreak::Anywhere || next.style().lineBreak() == LineBreak::Anywhere) {
-        // There is a soft wrap opportunity around every typographic character unit, including around any punctuation character
-        // or preserved white spaces, or in the middle of words.
-        return true;
-    }
-    auto& currentInlineTextItem = downcast<InlineTextItem>(current);
-    auto& nextInlineTextItem = downcast<InlineTextItem>(next);
-    if (currentInlineTextItem.isWhitespace()) {
-        // [ ][text] : after [whitespace] position is a soft wrap opportunity.
-        return true;
-    }
-    if (nextInlineTextItem.isWhitespace()) {
-        // [text][ ] (<span>text</span> )
-        // white-space: break-spaces: line breaking opportunity exists after every preserved white space character, but not before.
-        return nextInlineTextItem.style().whiteSpace() != WhiteSpace::BreakSpaces;
-    }
-    // Both current and next items are non-whitespace text.
-    // [text][text] : is a continuous content.
-    // [text-][text] : after [hyphen] position is a soft wrap opportunity.
-    return endsWithSoftWrapOpportunity(currentInlineTextItem, nextInlineTextItem);
-}
-
-size_t LineBreaker::nextWrapOpportunity(const InlineItems& inlineContent, unsigned startIndex)
-{
-    // 1. Find the start candidate by skipping leading non-content items e.g <span><span>start : skip "<span><span>"
-    // 2. Find the end candidate by skipping non-content items inbetween e.g. <span><span>start</span>end: skip "</span>"
-    // 3. Check if there's a soft wrap opportunity between the 2 candidate inline items and repeat.
-    // 4. Any force line break inbetween is considered as a wrap opportunity.
-
-    // [ex-][container start][container end][float][ample] (ex-<span></span><div style="float:left"></div>ample) : wrap index is at [ex-].
-    // [ex][container start][amp-][container start][le] (ex<span>amp-<span>ample) : wrap index is at [amp-].
-    // [ex-][container start][line break][ample] (ex-<span><br>ample) : wrap index is after [br].
-    auto end = inlineContent.size();
-
-    struct WrapContent {
-        WrapContent(size_t index, bool isAtLineBreak)
-            : m_index(index)
-            , m_isAtLineBreak(isAtLineBreak)
-        {
-        }
-        size_t operator*() const { return m_index; }
-        bool isAtLineBreak() const { return m_isAtLineBreak; }
-
-    private:
-        size_t m_index { 0 };
-        bool m_isAtLineBreak { false };
-    };
-    auto nextInlineItemWithContent = [&] (auto index) {
-        // Break at the first text/box/line break inline item.
-        for (; index < end; ++index) {
-            auto& inlineItem = *inlineContent[index];
-            if (inlineItem.isText() || inlineItem.isBox() || inlineItem.isLineBreak())
-                return WrapContent { index, inlineItem.isLineBreak() };
-        }
-        return WrapContent { end, false };
-    };
-
-    // Start at the first inline item with content.
-    // [container start][ex-] : start at [ex-]
-    auto startContent = nextInlineItemWithContent(startIndex);
-    if (startContent.isAtLineBreak()) {
-        // Content starts with a line break. The wrap position is after the line break.
-        return *startContent + 1;
-    }
-
-    while (*startContent != end) {
-        // 1. Find the next inline item with content.
-        // 2. Check if there's a soft wrap opportunity between the start and the next inline item.
-        auto nextContent = nextInlineItemWithContent(*startContent + 1);
-        if (*nextContent == end || nextContent.isAtLineBreak())
-            return *nextContent;
-        if (isAtSoftWrapOpportunity(*inlineContent[*startContent], *inlineContent[*nextContent])) {
-            // There's a soft wrap opportunity between the start and the nextContent.
-            // Now forward-find from the start position to see where we can actually wrap.
-            // [ex-][ample] vs. [ex-][container start][container end][ample]
-            // where [ex-] is startContent and [ample] is the nextContent.
-            auto candidateIndex = *startContent + 1;
-            for (; candidateIndex < *nextContent; ++candidateIndex) {
-                if (inlineContent[candidateIndex]->isContainerStart()) {
-                    // inline content and [container start] and [container end] form unbreakable content.
-                    // ex-<span></span>ample  : wrap opportunity is after "ex-".
-                    // ex-</span></span>ample : wrap opportunity is after "ex-</span></span>".
-                    // ex-</span><span>ample</span> : wrap opportunity is after "ex-</span>".
-                    // ex-<span><span>ample</span></span> : wrap opportunity is after "ex-".
-                    return candidateIndex;
-                }
-            }
-            return candidateIndex;
-        }
-        startContent = nextContent;
-    }
-    return end;
-}
-
-LineBreaker::ContinousContent::ContinousContent(const RunList& runs)
+ContinousContent::ContinousContent(const LineBreaker::RunList& runs)
     : m_runs(runs)
 {
     // Figure out the trailing collapsible state.
@@ -466,7 +365,7 @@
     }
 }
 
-bool LineBreaker::ContinousContent::hasTextContentOnly() const
+bool ContinousContent::hasTextContentOnly() const
 {
     // <span>text</span> is considered a text run even with the [container start][container end] inline items.
     // Due to commit boundary rules, we just need to check the first non-typeless inline item (can't have both [img] and [text])
@@ -479,7 +378,7 @@
     return false;
 }
 
-bool LineBreaker::ContinousContent::isVisuallyEmptyWhitespaceContentOnly() const
+bool ContinousContent::isVisuallyEmptyWhitespaceContentOnly() const
 {
     // [<span></span> ] [<span> </span>] [ <span style="padding: 0px;"></span>] are all considered visually empty whitespace content.
     // [<span style="border: 1px solid red"></span> ] while this is whitespace content only, it is not considered visually empty.
@@ -494,7 +393,7 @@
     return false;
 }
 
-Optional<unsigned> LineBreaker::ContinousContent::firstTextRunIndex() const
+Optional<size_t> ContinousContent::firstTextRunIndex() const
 {
     for (size_t index = 0; index < m_runs.size(); ++index) {
         if (m_runs[index].inlineItem.isText())
@@ -503,7 +402,7 @@
     return { };
 }
 
-Optional<unsigned> LineBreaker::ContinousContent::lastContentRunIndex() const
+Optional<size_t> ContinousContent::lastContentRunIndex() const
 {
     for (size_t index = m_runs.size(); index--;) {
         if (m_runs[index].inlineItem.isText() || m_runs[index].inlineItem.isBox())
@@ -512,7 +411,7 @@
     return { };
 }
 
-bool LineBreaker::ContinousContent::hasNonContentRunsOnly() const
+bool ContinousContent::hasNonContentRunsOnly() const
 {
     // <span></span> <- non content runs.
     for (auto& run : m_runs) {
@@ -524,7 +423,7 @@
     return true;
 }
 
-Optional<size_t> LineBreaker::ContinousContent::lastWrapOpportunityIndex() const
+Optional<size_t> ContinousContent::lastWrapOpportunityIndex() const
 {
     // <span style="white-space: pre">no_wrap</span><span>yes_wrap</span><span style="white-space: pre">no_wrap</span>.
     // [container start][no_wrap][container end][container start][yes_wrap][container end][container start][no_wrap][container end]
@@ -536,7 +435,7 @@
     return { };
 }
 
-void LineBreaker::ContinousContent::TrailingCollapsibleContent::reset()
+void ContinousContent::TrailingCollapsibleContent::reset()
 {
     isFullyCollapsible = false;
     width = 0_lu;

Modified: trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h (253933 => 253934)


--- trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h	2019-12-29 07:01:18 UTC (rev 253933)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h	2019-12-29 15:59:01 UTC (rev 253934)
@@ -34,6 +34,8 @@
 
 class InlineItem;
 class InlineTextItem;
+struct ContinousContent;
+struct WrappedTextContent;
 
 class LineBreaker {
 public:
@@ -49,10 +51,10 @@
             Split, // Partial content is on the current line.
             Push // Content is pushed to the next line.
         };
-        Action action;
+        Action action { Action::Keep };
         IsEndOfLine isEndOfLine { IsEndOfLine::No };
         struct PartialTrailingContent {
-            unsigned trailingRunIndex { 0 };
+            size_t trailingRunIndex { 0 };
             Optional<PartialRun> partialRun; // nullopt partial run means the trailing run is a complete run.
         };
         Optional<PartialTrailingContent> partialTrailingContent;
@@ -67,7 +69,6 @@
         InlineLayoutUnit logicalWidth { 0 };
     };
     using RunList = Vector<Run, 30>;
-    static size_t nextWrapOpportunity(const InlineItems&, unsigned startIndex);
 
     struct LineStatus {
         InlineLayoutUnit availableWidth { 0 };
@@ -90,42 +91,6 @@
     // [content]
     // [container start][span1][container end][between][container start][span2][container end]
     // see https://drafts.csswg.org/css-text-3/#line-break-details
-    struct ContinousContent {
-        ContinousContent(const RunList&);
-
-        const RunList& runs() const { return m_runs; }
-        bool isEmpty() const { return m_runs.isEmpty(); }
-        bool hasTextContentOnly() const;
-        bool isVisuallyEmptyWhitespaceContentOnly() const;
-        bool hasNonContentRunsOnly() const;
-        size_t size() const { return m_runs.size(); }
-        InlineLayoutUnit width() const { return m_width; }
-        InlineLayoutUnit nonCollapsibleWidth() const { return m_width - m_trailingCollapsibleContent.width; }
-
-        bool hasTrailingCollapsibleContent() const { return !!m_trailingCollapsibleContent.width; }
-        bool isTrailingContentFullyCollapsible() const { return m_trailingCollapsibleContent.isFullyCollapsible; }
-        Optional<size_t> lastWrapOpportunityIndex() const;
-
-        Optional<unsigned> firstTextRunIndex() const;
-        Optional<unsigned> lastContentRunIndex() const;
-
-    private:
-        RunList m_runs;
-        struct TrailingCollapsibleContent {
-            void reset();
-
-            bool isFullyCollapsible { false };
-            InlineLayoutUnit width { 0 };
-        };
-        TrailingCollapsibleContent m_trailingCollapsibleContent;
-        InlineLayoutUnit m_width { 0 };
-    };
-
-    struct WrappedTextContent {
-        unsigned trailingRunIndex { 0 };
-        bool contentOverflows { false };
-        Optional<PartialRun> partialTrailingRun;
-    };
     Optional<WrappedTextContent> wrapTextContent(const RunList&, const LineStatus&) const;
     Result tryWrappingInlineContent(const ContinousContent&, const LineStatus&) const;
     Optional<PartialRun> tryBreakingTextRun(const Run& overflowRun, InlineLayoutUnit availableWidth) const;

Modified: trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp (253933 => 253934)


--- trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp	2019-12-29 07:01:18 UTC (rev 253933)
+++ trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp	2019-12-29 15:59:01 UTC (rev 253934)
@@ -34,6 +34,144 @@
 namespace WebCore {
 namespace Layout {
 
+static bool endsWithSoftWrapOpportunity(const InlineTextItem& currentTextItem, const InlineTextItem& nextInlineTextItem)
+{
+    ASSERT(!nextInlineTextItem.isWhitespace());
+    // We are at the position after a whitespace.
+    if (currentTextItem.isWhitespace())
+        return true;
+    // When both these non-whitespace runs belong to the same layout box, it's guaranteed that
+    // they are split at a soft breaking opportunity. See InlineTextItem::moveToNextBreakablePosition.
+    if (&currentTextItem.layoutBox() == &nextInlineTextItem.layoutBox())
+        return true;
+    // Now we need to collect at least 3 adjacent characters to be able to make a descision whether the previous text item ends with breaking opportunity.
+    // [ex-][ample] <- second to last[x] last[-] current[a]
+    // We need at least 1 character in the current inline text item and 2 more from previous inline items.
+    auto previousContent = currentTextItem.layoutBox().textContext()->content;
+    auto lineBreakIterator = LazyLineBreakIterator { nextInlineTextItem.layoutBox().textContext()->content };
+    auto previousContentLength = previousContent.length();
+    // FIXME: We should look into the entire uncommitted content for more text context.
+    UChar lastCharacter = previousContentLength ? previousContent[previousContentLength - 1] : 0;
+    UChar secondToLastCharacter = previousContentLength > 1 ? previousContent[previousContentLength - 2] : 0;
+    lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
+    // Now check if we can break right at the inline item boundary.
+    // With the [ex-ample], findNextBreakablePosition should return the startPosition (0).
+    // FIXME: Check if there's a more correct way of finding breaking opportunities.
+    return !TextUtil::findNextBreakablePosition(lineBreakIterator, 0, nextInlineTextItem.style());
+}
+
+static bool isAtSoftWrapOpportunity(const InlineItem& current, const InlineItem& next)
+{
+    // "is at" simple means that there's a soft wrap opportunity right after the [current].
+    // [text][ ][text][container start]... (<div>text content<span>..</div>)
+    // soft wrap indexes: 0 and 1 definitely, 2 depends on the content after the [container start].
+
+    // https://drafts.csswg.org/css-text-3/#line-break-details
+    // Figure out if the new incoming content puts the uncommitted content on a soft wrap opportunity.
+    // e.g. [container start][prior_continuous_content][container end] (<span>prior_continuous_content</span>)
+    // An incoming <img> box would enable us to commit the "<span>prior_continuous_content</span>" content
+    // but an incoming text content would not necessarily.
+    ASSERT(current.isText() || current.isBox());
+    ASSERT(next.isText() || next.isBox());
+    if (current.isBox() || next.isBox()) {
+        // [text][container start][container end][inline box] (text<span></span><img>) : there's a soft wrap opportunity between the [text] and [img].
+        // The line breaking behavior of a replaced element or other atomic inline is equivalent to an ideographic character.
+        return true;
+    }
+    if (current.style().lineBreak() == LineBreak::Anywhere || next.style().lineBreak() == LineBreak::Anywhere) {
+        // There is a soft wrap opportunity around every typographic character unit, including around any punctuation character
+        // or preserved white spaces, or in the middle of words.
+        return true;
+    }
+    auto& currentInlineTextItem = downcast<InlineTextItem>(current);
+    auto& nextInlineTextItem = downcast<InlineTextItem>(next);
+    if (currentInlineTextItem.isWhitespace()) {
+        // [ ][text] : after [whitespace] position is a soft wrap opportunity.
+        return true;
+    }
+    if (nextInlineTextItem.isWhitespace()) {
+        // [text][ ] (<span>text</span> )
+        // white-space: break-spaces: line breaking opportunity exists after every preserved white space character, but not before.
+        return nextInlineTextItem.style().whiteSpace() != WhiteSpace::BreakSpaces;
+    }
+    // Both current and next items are non-whitespace text.
+    // [text][text] : is a continuous content.
+    // [text-][text] : after [hyphen] position is a soft wrap opportunity.
+    return endsWithSoftWrapOpportunity(currentInlineTextItem, nextInlineTextItem);
+}
+
+static size_t nextWrapOpportunity(const InlineItems& inlineContent, unsigned startIndex)
+{
+    // 1. Find the start candidate by skipping leading non-content items e.g <span><span>start : skip "<span><span>"
+    // 2. Find the end candidate by skipping non-content items inbetween e.g. <span><span>start</span>end: skip "</span>"
+    // 3. Check if there's a soft wrap opportunity between the 2 candidate inline items and repeat.
+    // 4. Any force line break inbetween is considered as a wrap opportunity.
+
+    // [ex-][container start][container end][float][ample] (ex-<span></span><div style="float:left"></div>ample) : wrap index is at [ex-].
+    // [ex][container start][amp-][container start][le] (ex<span>amp-<span>ample) : wrap index is at [amp-].
+    // [ex-][container start][line break][ample] (ex-<span><br>ample) : wrap index is after [br].
+    auto end = inlineContent.size();
+
+    struct WrapContent {
+        WrapContent(size_t index, bool isAtLineBreak)
+            : m_index(index)
+            , m_isAtLineBreak(isAtLineBreak)
+        {
+        }
+        size_t operator*() const { return m_index; }
+        bool isAtLineBreak() const { return m_isAtLineBreak; }
+
+    private:
+        size_t m_index { 0 };
+        bool m_isAtLineBreak { false };
+    };
+    auto nextInlineItemWithContent = [&] (auto index) {
+        // Break at the first text/box/line break inline item.
+        for (; index < end; ++index) {
+            auto& inlineItem = *inlineContent[index];
+            if (inlineItem.isText() || inlineItem.isBox() || inlineItem.isLineBreak())
+                return WrapContent { index, inlineItem.isLineBreak() };
+        }
+        return WrapContent { end, false };
+    };
+
+    // Start at the first inline item with content.
+    // [container start][ex-] : start at [ex-]
+    auto startContent = nextInlineItemWithContent(startIndex);
+    if (startContent.isAtLineBreak()) {
+        // Content starts with a line break. The wrap position is after the line break.
+        return *startContent + 1;
+    }
+
+    while (*startContent != end) {
+        // 1. Find the next inline item with content.
+        // 2. Check if there's a soft wrap opportunity between the start and the next inline item.
+        auto nextContent = nextInlineItemWithContent(*startContent + 1);
+        if (*nextContent == end || nextContent.isAtLineBreak())
+            return *nextContent;
+        if (isAtSoftWrapOpportunity(*inlineContent[*startContent], *inlineContent[*nextContent])) {
+            // There's a soft wrap opportunity between the start and the nextContent.
+            // Now forward-find from the start position to see where we can actually wrap.
+            // [ex-][ample] vs. [ex-][container start][container end][ample]
+            // where [ex-] is startContent and [ample] is the nextContent.
+            auto candidateIndex = *startContent + 1;
+            for (; candidateIndex < *nextContent; ++candidateIndex) {
+                if (inlineContent[candidateIndex]->isContainerStart()) {
+                    // inline content and [container start] and [container end] form unbreakable content.
+                    // ex-<span></span>ample  : wrap opportunity is after "ex-".
+                    // ex-</span></span>ample : wrap opportunity is after "ex-</span></span>".
+                    // ex-</span><span>ample</span> : wrap opportunity is after "ex-</span>".
+                    // ex-<span><span>ample</span></span> : wrap opportunity is after "ex-".
+                    return candidateIndex;
+                }
+            }
+            return candidateIndex;
+        }
+        startContent = nextContent;
+    }
+    return end;
+}
+
 struct LineCandidateContent {
     void append(const InlineItem&, Optional<InlineLayoutUnit> logicalWidth);
 
@@ -190,7 +328,7 @@
     // 1. Simply add any overflow content from the previous line to the candidate content. It's always a text content.
     // 2. Find the next soft wrap position or explicit line break.
     // 3. Collect floats between the inline content.
-    auto softWrapOpportunityIndex = LineBreaker::nextWrapOpportunity(m_inlineItems, inlineItemIndex);
+    auto softWrapOpportunityIndex = nextWrapOpportunity(m_inlineItems, inlineItemIndex);
     // softWrapOpportunityIndex == m_inlineItems.size() means we don't have any wrap opportunity in this content.
     ASSERT(softWrapOpportunityIndex <= m_inlineItems.size());
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to