[LFC][IFC] Construct dedicated runs when the inline element requires it.
authorzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 12 Nov 2018 16:18:08 +0000 (16:18 +0000)
committerzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 12 Nov 2018 16:18:08 +0000 (16:18 +0000)
https://bugs.webkit.org/show_bug.cgi?id=191509

Reviewed by Antti Koivisto.

In certain cases, a run can overlap multiple inline elements like this:

<span>normal text content</span><span style="position: relative; left: 10px;">but this one needs a dedicated run</span><span>end of text</span>

The content above generates one long run <normal text contentbut this one needs dedicated runend of text> <- input to line breaking.
However, since the middle run is positioned, it needs to be moved independently from the rest of the content, hence it needs a dedicated inline run.

* layout/inlineformatting/InlineFormattingContext.cpp:
(WebCore::Layout::InlineFormattingContext::layout const):
(WebCore::Layout::contentRequiresSeparateRun):
(WebCore::Layout::InlineFormattingContext::splitInlineRunIfNeeded const):
(WebCore::Layout::InlineFormattingContext::postProcessInlineRuns const):
(WebCore::Layout::InlineFormattingContext::closeLine const):
(WebCore::Layout::InlineFormattingContext::appendContentToLine const):
(WebCore::Layout::InlineFormattingContext::layoutInlineContent const):
(WebCore::Layout::InlineFormattingContext::instrinsicWidthConstraints const):
* layout/inlineformatting/InlineFormattingContext.h:
(WebCore::Layout::InlineFormattingContext::inlineFormattingState const):
* layout/inlineformatting/InlineLineBreaker.cpp:
(WebCore::Layout::InlineLineBreaker::nextRun): mid-word breaking is not implemented yet.
* layout/inlineformatting/InlineRun.h:
(WebCore::Layout::InlineRun::overlapsMultipleInlineItems const):
* layout/inlineformatting/InlineRunProvider.cpp:
(WebCore::Layout::InlineRunProvider::processInlineTextItem):
* layout/inlineformatting/InlineRunProvider.h:
(WebCore::Layout::InlineRunProvider::Run::TextContext::expand):
(WebCore::Layout::InlineRunProvider::Run::textContext):
(WebCore::Layout::InlineRunProvider::Run::TextContext::setStart): Deleted.
(WebCore::Layout::InlineRunProvider::Run::TextContext::setLength): Deleted.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@238087 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/layout/inlineformatting/InlineFormattingContext.cpp
Source/WebCore/layout/inlineformatting/InlineFormattingContext.h
Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp
Source/WebCore/layout/inlineformatting/InlineRun.h
Source/WebCore/layout/inlineformatting/InlineRunProvider.cpp
Source/WebCore/layout/inlineformatting/InlineRunProvider.h

index c58cbc2..579b4a3 100644 (file)
@@ -1,3 +1,40 @@
+2018-11-12  Zalan Bujtas  <zalan@apple.com>
+
+        [LFC][IFC] Construct dedicated runs when the inline element requires it.
+        https://bugs.webkit.org/show_bug.cgi?id=191509
+
+        Reviewed by Antti Koivisto.
+
+        In certain cases, a run can overlap multiple inline elements like this:
+
+        <span>normal text content</span><span style="position: relative; left: 10px;">but this one needs a dedicated run</span><span>end of text</span>
+
+        The content above generates one long run <normal text contentbut this one needs dedicated runend of text> <- input to line breaking.
+        However, since the middle run is positioned, it needs to be moved independently from the rest of the content, hence it needs a dedicated inline run.
+
+        * layout/inlineformatting/InlineFormattingContext.cpp:
+        (WebCore::Layout::InlineFormattingContext::layout const):
+        (WebCore::Layout::contentRequiresSeparateRun):
+        (WebCore::Layout::InlineFormattingContext::splitInlineRunIfNeeded const):
+        (WebCore::Layout::InlineFormattingContext::postProcessInlineRuns const):
+        (WebCore::Layout::InlineFormattingContext::closeLine const):
+        (WebCore::Layout::InlineFormattingContext::appendContentToLine const):
+        (WebCore::Layout::InlineFormattingContext::layoutInlineContent const):
+        (WebCore::Layout::InlineFormattingContext::instrinsicWidthConstraints const):
+        * layout/inlineformatting/InlineFormattingContext.h:
+        (WebCore::Layout::InlineFormattingContext::inlineFormattingState const):
+        * layout/inlineformatting/InlineLineBreaker.cpp:
+        (WebCore::Layout::InlineLineBreaker::nextRun): mid-word breaking is not implemented yet.
+        * layout/inlineformatting/InlineRun.h:
+        (WebCore::Layout::InlineRun::overlapsMultipleInlineItems const):
+        * layout/inlineformatting/InlineRunProvider.cpp:
+        (WebCore::Layout::InlineRunProvider::processInlineTextItem):
+        * layout/inlineformatting/InlineRunProvider.h:
+        (WebCore::Layout::InlineRunProvider::Run::TextContext::expand):
+        (WebCore::Layout::InlineRunProvider::Run::textContext):
+        (WebCore::Layout::InlineRunProvider::Run::TextContext::setStart): Deleted.
+        (WebCore::Layout::InlineRunProvider::Run::TextContext::setLength): Deleted.
+
 2018-11-12  Jer Noble  <jer.noble@apple.com>
 
         [MSE] Frame re-ordering can cause iframes to never be enqueued
index d7b463e..784e11f 100644 (file)
@@ -58,9 +58,7 @@ void InlineFormattingContext::layout() const
 
     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> inline formatting context -> formatting root(" << &root() << ")");
 
-    auto& inlineFormattingState = downcast<InlineFormattingState>(formattingState());
-    InlineRunProvider inlineRunProvider(inlineFormattingState);
-
+    InlineRunProvider inlineRunProvider(inlineFormattingState());
     collectInlineContent(inlineRunProvider);
     // Compute width/height for non-text content.
     for (auto& inlineRun : inlineRunProvider.runs()) {
@@ -127,6 +125,119 @@ void InlineFormattingContext::initializeNewLine(Line& line) const
     line.init(logicalRect);
 }
 
+bool InlineFormattingContext::contentRequiresSeparateRun(const InlineItem& inlineItem) const
+{
+    // FIXME: This is way too inefficient. We should pre-mark the runs instead while flattening the inline formatting context.
+    for (auto* inlineContainer = inlineItem.layoutBox().parent(); inlineContainer != &root(); inlineContainer = inlineContainer->parent()) {
+        if (inlineContainer->isPositioned())
+            return true;
+    }
+    return false;
+}
+
+void InlineFormattingContext::splitInlineRunIfNeeded(const InlineRun& inlineRun, InlineRuns& splitRuns) const
+{
+    if (!inlineRun.overlapsMultipleInlineItems())
+        return;
+
+    ASSERT(inlineRun.textContext());
+    // In certain cases, a run can overlap multiple inline elements like this:
+    // <span>normal text content</span><span style="position: relative; left: 10px;">but this one needs a dedicated run</span><span>end of text</span>
+    // The content above generates one long run <normal text contentbut this one needs dedicated runend of text>
+    // However, since the middle run is positioned, it needs to be moved independently from the rest of the content, hence it needs a dedicated inline run.
+
+    // 1. Start with the first inline item (element) and travers the list until
+    // 2. either find an inline item that needs a dedicated run or we reach the end of the run
+    // 3. Shrink the original inline run and create a new one.
+    auto& inlineContent = inlineFormattingState().inlineContent();
+    auto textUtil = TextUtil { inlineContent };
+
+    auto split=[&](const auto& inlineItem, auto startPosition, auto length, auto contentStart) {
+        auto width = textUtil.width(inlineItem, startPosition, length, contentStart);
+
+        auto run = InlineRun { { inlineRun.logicalTop(), contentStart, width, inlineRun.height() }, inlineItem };
+        run.setTextContext({ startPosition, length });
+        splitRuns.append(run);
+        return contentStart + width;
+    };
+
+    auto contentStart = inlineRun.logicalLeft();
+    auto startPosition = inlineRun.textContext()->start();
+    auto remaningLength = inlineRun.textContext()->length();
+
+    unsigned uncommittedLength = 0;
+    InlineItem* firstUncommittedInlineItem = nullptr;
+    for (auto iterator = inlineContent.find<const InlineItem&, InlineItemHashTranslator>(inlineRun.inlineItem()); iterator != inlineContent.end() && remaningLength > 0; ++iterator) {
+        auto& inlineItem = **iterator;
+
+        if (!contentRequiresSeparateRun(inlineItem)) {
+            uncommittedLength += std::min(remaningLength, inlineItem.textContent().length() - startPosition);
+            firstUncommittedInlineItem = !firstUncommittedInlineItem ? &inlineItem : firstUncommittedInlineItem;
+            continue;
+        }
+
+        // Commit the items that don't need dedicated run.
+        if (firstUncommittedInlineItem) {
+            contentStart = split(*firstUncommittedInlineItem, startPosition, uncommittedLength, contentStart);
+
+            remaningLength -= uncommittedLength;
+            startPosition = 0;
+            uncommittedLength = 0;
+        }
+
+        // Create a dedicated run for this inline item.
+        auto length = std::min(remaningLength, inlineItem.textContent().length() - startPosition);
+        contentStart = split(inlineItem, startPosition, length, contentStart);
+
+        startPosition = 0;
+        remaningLength -= length;
+        firstUncommittedInlineItem = nullptr;
+    }
+
+    // Either all inline elements needed dedicated runs or neither of them.
+    if (!remaningLength || remaningLength == inlineRun.textContext()->length())
+        return;
+
+    ASSERT(remaningLength == uncommittedLength);
+    split(*firstUncommittedInlineItem, startPosition, uncommittedLength, contentStart);
+}
+
+void InlineFormattingContext::postProcessInlineRuns(Line& line, IsLastLine isLastLine, Line::RunRange runRange) const
+{
+    auto& inlineFormattingState = this->inlineFormattingState();
+    Geometry::alignRuns(inlineFormattingState, root().style().textAlign(), line, runRange, isLastLine);
+
+    auto& inlineRuns = inlineFormattingState.inlineRuns();
+    ASSERT(*runRange.lastRunIndex < inlineRuns.size());
+
+    auto runIndex = *runRange.firstRunIndex;
+    auto& lastInlineRun = inlineRuns[*runRange.lastRunIndex];
+    while (runIndex < inlineRuns.size()) {
+        auto& inlineRun = inlineRuns[runIndex];
+        auto isLastRunInRange = &inlineRun == &lastInlineRun;
+
+        InlineRuns splitRuns;
+        splitInlineRunIfNeeded(inlineRun, splitRuns);
+        if (!splitRuns.isEmpty()) {
+            ASSERT(splitRuns.size() > 1);
+            // Replace the continous run with new ones.
+            // Reuse the original one.
+            auto& firstRun = splitRuns.first();
+            inlineRun.setWidth(firstRun.width());
+            inlineRun.textContext()->setLength(firstRun.textContext()->length());
+            splitRuns.remove(0);
+            // Insert the rest.
+            for (auto& splitRun : splitRuns)
+                inlineRuns.insert(++runIndex, splitRun);
+        }
+
+        if (isLastRunInRange)
+            break;
+
+        ++runIndex;
+    }
+}
+
 void InlineFormattingContext::closeLine(Line& line, IsLastLine isLastLine) const
 {
     auto runRange = line.close();
@@ -135,7 +246,7 @@ void InlineFormattingContext::closeLine(Line& line, IsLastLine isLastLine) const
     if (!runRange.firstRunIndex)
         return;
 
-    Geometry::alignRuns(downcast<InlineFormattingState>(formattingState()), root().style().textAlign(), line, runRange, isLastLine);
+    postProcessInlineRuns(line, isLastLine, runRange);
 }
 
 void InlineFormattingContext::appendContentToLine(Line& line, const InlineLineBreaker::Run& run) const
@@ -144,13 +255,13 @@ void InlineFormattingContext::appendContentToLine(Line& line, const InlineLineBr
     line.appendContent(run);
 
     if (root().style().textAlign() == TextAlignMode::Justify)
-        Geometry::computeExpansionOpportunities(downcast<InlineFormattingState>(formattingState()), run.content, lastRunType.value_or(InlineRunProvider::Run::Type::NonWhitespace));
+        Geometry::computeExpansionOpportunities(inlineFormattingState(), run.content, lastRunType.value_or(InlineRunProvider::Run::Type::NonWhitespace));
 }
 
 void InlineFormattingContext::layoutInlineContent(const InlineRunProvider& inlineRunProvider) const
 {
     auto& layoutState = this->layoutState();
-    auto& inlineFormattingState = downcast<InlineFormattingState>(formattingState());
+    auto& inlineFormattingState = this->inlineFormattingState();
     auto floatingContext = FloatingContext { inlineFormattingState.floatingState() };
 
     Line line(inlineFormattingState);
@@ -328,7 +439,7 @@ FormattingContext::InstrinsicWidthConstraints InlineFormattingContext::instrinsi
     if (auto instrinsicWidthConstraints = formattingStateForRoot.instrinsicWidthConstraints(root()))
         return *instrinsicWidthConstraints;
 
-    auto& inlineFormattingState = downcast<InlineFormattingState>(formattingState());
+    auto& inlineFormattingState = this->inlineFormattingState();
     InlineRunProvider inlineRunProvider(inlineFormattingState);
     collectInlineContent(inlineRunProvider);
 
index 034175f..a3c22a7 100644 (file)
@@ -30,6 +30,7 @@
 #include "DisplayBox.h"
 #include "FormattingContext.h"
 #include "InlineLineBreaker.h"
+#include "InlineRun.h"
 #include <wtf/IsoMalloc.h>
 
 namespace WebCore {
@@ -111,6 +112,9 @@ private:
     void initializeNewLine(Line&) const;
     void closeLine(Line&, IsLastLine) const;
     void appendContentToLine(Line&, const InlineLineBreaker::Run&) const;
+    void postProcessInlineRuns(Line&, IsLastLine, Line::RunRange) const;
+    void splitInlineRunIfNeeded(const InlineRun&, InlineRuns& splitRuns) const;
+    bool contentRequiresSeparateRun(const InlineItem&) const;
 
     void layoutFormattingContextRoot(const Box&) const;
     void computeWidthAndHeightForReplacedInlineBox(const Box&) const;
@@ -121,6 +125,8 @@ private:
 
     void collectInlineContent(InlineRunProvider&) const;
     InstrinsicWidthConstraints instrinsicWidthConstraints() const override;
+
+    InlineFormattingState& inlineFormattingState() const { return downcast<InlineFormattingState>(formattingState()); }
 };
 
 }
index f9e4247..d2e10df 100644 (file)
@@ -54,7 +54,6 @@ std::optional<InlineLineBreaker::Run> InlineLineBreaker::nextRun(LayoutUnit cont
     // Adjust the current run if it is split midword.
     if (m_splitPosition) {
         ASSERT(currentInlineRun.isText());
-        currentInlineRun.textContext()->setStart(*m_splitPosition);
         m_splitPosition = std::nullopt;
     }
 
index 5f0e558..63bb0ca 100644 (file)
@@ -43,6 +43,7 @@ struct InlineRun {
 
     LayoutUnit width() const { return m_logicalRect.width(); }
     LayoutUnit height() const { return m_logicalRect.height(); }
+    bool overlapsMultipleInlineItems() const;
 
     void setWidth(LayoutUnit width) { m_logicalRect.setWidth(width); }
     void setLogicalLeft(LayoutUnit logicalLeft) { m_logicalRect.setLeft(logicalLeft); }
@@ -98,6 +99,16 @@ inline InlineRun::TextContext::TextContext(ItemPosition start, unsigned length)
 {
 }
 
+inline bool InlineRun::overlapsMultipleInlineItems() const
+{
+    // Only text content can overlap multiple inline elements.
+    if (!m_textContext)
+        return false;
+
+    auto endPosition = m_textContext->start() + m_textContext->length();
+    return endPosition > m_inlineItem.textContent().length();
+}
+
 }
 }
 #endif
index 9c1658a..9abb3cf 100644 (file)
@@ -130,10 +130,9 @@ void InlineRunProvider::processInlineTextItem(const InlineItem& inlineItem)
         auto isWhitespaceRun = isWhitespace(text[currentItemPosition], style.preserveNewline());
         auto length = isWhitespaceRun ? moveToNextNonWhitespacePosition(inlineItem, currentItemPosition) : moveToNextBreakablePosition(inlineItem, currentItemPosition);
 
-        if (isContinousContent(isWhitespaceRun ? InlineRunProvider::Run::Type::Whitespace : InlineRunProvider::Run::Type::NonWhitespace, inlineItem)) {
-            auto textContext = m_inlineRuns.last().textContext();
-            textContext->setLength(textContext->length() + length);
-        } else {
+        if (isContinousContent(isWhitespaceRun ? InlineRunProvider::Run::Type::Whitespace : InlineRunProvider::Run::Type::NonWhitespace, inlineItem))
+            m_inlineRuns.last().textContext()->expand(length);
+        else {
             m_inlineRuns.append(isWhitespaceRun ? InlineRunProvider::Run::createWhitespaceRun(inlineItem, currentItemPosition, length, style.collapseWhiteSpace())
                 : InlineRunProvider::Run::createNonWhitespaceRun(inlineItem, currentItemPosition, length));
         }
index 10a4cde..28341e1 100644 (file)
@@ -82,10 +82,11 @@ public:
             unsigned length() const { return m_length; }
             bool isCollapsed() const { return m_isCollapsed == IsCollapsed::Yes; }
 
-            void setStart(ItemPosition start) { m_start = start; }
-            void setLength(unsigned length) { m_length = length; }
-
         private:
+            friend class InlineRunProvider;
+
+            void expand(unsigned length) { m_length += length; }
+
             ItemPosition m_start { 0 };
             unsigned m_length { 0 };
             IsCollapsed m_isCollapsed { IsCollapsed::No };
@@ -96,7 +97,10 @@ public:
         const InlineItem& inlineItem() const { return m_inlineItem; }
 
     private:
+        friend class InlineRunProvider;
+
         Run(const InlineItem&, Type, std::optional<TextContext>);
+        std::optional<TextContext>& textContext() { return m_textContext; }
 
         const Type m_type;
         const InlineItem& m_inlineItem;