[LFC][IFC] Paint partial trailing run with hyphen when needed
https://bugs.webkit.org/show_bug.cgi?id=204953
<rdar://problem/57705169>

Reviewed by Antti Koivisto.

When LineBreaker comes back with a partial content that needs hyphen, we need to make sure this information
ends up in the final Display::Run so that the rendered content includes the hyphen string. Note that this only needs to
be done when the content does _not_ have the hyphen already (opportunity vs. oppor-tunity).
(This patch also renames trailingPartial to partialTrailing because the fact that it is partial run is more important than that it is trailing run.)

* layout/displaytree/DisplayRun.h:
(WebCore::Display::Run::TextContext::TextContext):
(WebCore::Display::Run::TextContext::needsHyphen const):
(WebCore::Display::Run::TextContext::setNeedsHyphen):
(WebCore::Display::Run::textContext):
* layout/inlineformatting/InlineFormattingContext.cpp:
(WebCore::Layout::InlineFormattingContext::lineLayout):
(WebCore::Layout::InlineFormattingContext::setDisplayBoxesForLine):
* layout/inlineformatting/InlineLineBreaker.cpp:
(WebCore::Layout::LineBreaker::breakingContextForInlineContent):
(WebCore::Layout::LineBreaker::wordBreakingBehavior const):
* layout/inlineformatting/InlineLineBreaker.h:
* layout/inlineformatting/LineLayoutContext.cpp:
(WebCore::Layout::LineLayoutContext::layoutLine):
(WebCore::Layout::LineLayoutContext::close):
(WebCore::Layout::LineLayoutContext::processUncommittedContent):
* layout/inlineformatting/LineLayoutContext.h:
* layout/integration/LayoutIntegrationLineLayout.cpp:
(WebCore::LayoutIntegration::LineLayout::paint):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@253214 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 3be3c29..513754a 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,36 @@
+2019-12-06  Zalan Bujtas  <zalan@apple.com>
+
+        [LFC][IFC] Paint partial trailing run with hyphen when needed
+        https://bugs.webkit.org/show_bug.cgi?id=204953
+        <rdar://problem/57705169>
+
+        Reviewed by Antti Koivisto.
+
+        When LineBreaker comes back with a partial content that needs hyphen, we need to make sure this information
+        ends up in the final Display::Run so that the rendered content includes the hyphen string. Note that this only needs to
+        be done when the content does _not_ have the hyphen already (opportunity vs. oppor-tunity).
+        (This patch also renames trailingPartial to partialTrailing because the fact that it is partial run is more important than that it is trailing run.)
+
+        * layout/displaytree/DisplayRun.h:
+        (WebCore::Display::Run::TextContext::TextContext):
+        (WebCore::Display::Run::TextContext::needsHyphen const):
+        (WebCore::Display::Run::TextContext::setNeedsHyphen):
+        (WebCore::Display::Run::textContext):
+        * layout/inlineformatting/InlineFormattingContext.cpp:
+        (WebCore::Layout::InlineFormattingContext::lineLayout):
+        (WebCore::Layout::InlineFormattingContext::setDisplayBoxesForLine):
+        * layout/inlineformatting/InlineLineBreaker.cpp:
+        (WebCore::Layout::LineBreaker::breakingContextForInlineContent):
+        (WebCore::Layout::LineBreaker::wordBreakingBehavior const):
+        * layout/inlineformatting/InlineLineBreaker.h:
+        * layout/inlineformatting/LineLayoutContext.cpp:
+        (WebCore::Layout::LineLayoutContext::layoutLine):
+        (WebCore::Layout::LineLayoutContext::close):
+        (WebCore::Layout::LineLayoutContext::processUncommittedContent):
+        * layout/inlineformatting/LineLayoutContext.h:
+        * layout/integration/LayoutIntegrationLineLayout.cpp:
+        (WebCore::LayoutIntegration::LineLayout::paint):
+
 2019-12-06  Chris Dumez  <cdumez@apple.com>
 
         Prevent synchronous XHR in beforeunload / unload event handlers
diff --git a/Source/WebCore/layout/displaytree/DisplayRun.h b/Source/WebCore/layout/displaytree/DisplayRun.h
index 4488118..41db175 100644
--- a/Source/WebCore/layout/displaytree/DisplayRun.h
+++ b/Source/WebCore/layout/displaytree/DisplayRun.h
@@ -45,7 +45,7 @@
         WTF_MAKE_STRUCT_FAST_ALLOCATED;
     public:
         struct ExpansionContext;
-        TextContext(unsigned position, unsigned length, const String&, Optional<ExpansionContext> = { });
+        TextContext(unsigned position, unsigned length, const String&);
 
         unsigned start() const { return m_start; }
         unsigned end() const { return start() + length(); }
@@ -59,11 +59,15 @@
         void setExpansion(ExpansionContext expansionContext) { m_expansionContext = expansionContext; }
         Optional<ExpansionContext> expansion() const { return m_expansionContext; }
 
+        bool needsHyphen() const { return m_needsHyphen; }
+
         void expand(unsigned expandedLength);
+        void setNeedsHyphen() { m_needsHyphen = true; }
 
     private:
         unsigned m_start { 0 };
         unsigned m_length { 0 };
+        bool m_needsHyphen { false };
         String m_contentString;
         Optional<ExpansionContext> m_expansionContext;
     };
@@ -94,6 +98,7 @@
 
     void setTextContext(const TextContext&& textContext) { m_textContext.emplace(textContext); }
     const Optional<TextContext>& textContext() const { return m_textContext; }
+    Optional<TextContext>& textContext() { return m_textContext; }
 
     void setImage(CachedImage& image) { m_cachedImage = &image; }
     CachedImage* image() const { return m_cachedImage; }
@@ -120,11 +125,10 @@
 {
 }
 
-inline Run::TextContext::TextContext(unsigned start, unsigned length, const String& contentString, Optional<ExpansionContext> expansionContext)
+inline Run::TextContext::TextContext(unsigned start, unsigned length, const String& contentString)
     : m_start(start)
     , m_length(length)
     , m_contentString(contentString)
-    , m_expansionContext(expansionContext)
 {
 }
 
diff --git a/Source/WebCore/layout/inlineformatting/InlineFormattingContext.cpp b/Source/WebCore/layout/inlineformatting/InlineFormattingContext.cpp
index 621fb8b..b723484 100644
--- a/Source/WebCore/layout/inlineformatting/InlineFormattingContext.cpp
+++ b/Source/WebCore/layout/inlineformatting/InlineFormattingContext.cpp
@@ -94,24 +94,24 @@
     auto& inlineItems = formattingState().inlineItems();
     auto lineLogicalTop = geometryForBox(root()).contentBoxTop();
     unsigned leadingInlineItemIndex = 0;
-    Optional<LineLayoutContext::PartialContent> leadingPartialContent;
+    Optional<unsigned> partialLeadingContentLength;
     auto lineBuilder = LineBuilder { *this, root().style().textAlign(), LineBuilder::SkipAlignment::No };
     auto lineLayoutContext = LineLayoutContext { *this, root(), inlineItems };
 
     while (leadingInlineItemIndex < inlineItems.size()) {
         lineBuilder.initialize(constraintsForLine(usedHorizontalValues, lineLogicalTop));
-        auto lineContent = lineLayoutContext.layoutLine(lineBuilder, leadingInlineItemIndex, leadingPartialContent);
+        auto lineContent = lineLayoutContext.layoutLine(lineBuilder, leadingInlineItemIndex, partialLeadingContentLength);
         setDisplayBoxesForLine(lineContent, usedHorizontalValues);
 
-        leadingPartialContent = { };
+        partialLeadingContentLength = { };
         if (lineContent.trailingInlineItemIndex) {
             lineLogicalTop = lineContent.lineBox.logicalBottom();
             // When the trailing content is partial, we need to reuse the last InlinItem.
-            if (lineContent.overflowPartialContent) {
+            if (lineContent.partialContent) {
                 leadingInlineItemIndex = *lineContent.trailingInlineItemIndex;
                 // Turn previous line's overflow content length into the next line's leading content partial length.
                 // "sp<->litcontent" -> overflow length: 10 -> leading partial content length: 10. 
-                leadingPartialContent = LineLayoutContext::PartialContent { lineContent.overflowPartialContent->length };
+                partialLeadingContentLength = lineContent.partialContent->overlfowContentLength;
             } else
                 leadingInlineItemIndex = *lineContent.trailingInlineItemIndex + 1;
         } else {
@@ -427,10 +427,9 @@
     }
 
     auto& inlineContent = formattingState.ensureDisplayInlineContent();
-
     auto lineIndex = inlineContent.lineBoxes.size();
     inlineContent.lineBoxes.append(LineBox { lineContent.lineBox });
-
+    Optional<unsigned> lastTextItemIndex;
     // Compute box final geometry.
     auto& lineRuns = lineContent.runList;
     for (unsigned index = 0; index < lineRuns.size(); ++index) {
@@ -483,6 +482,7 @@
         }
 
         if (lineRun.isText()) {
+            lastTextItemIndex = inlineContent.runs.size() - 1;
             auto firstRunForLayoutBox = !index || &lineRuns[index - 1].layoutBox() != &layoutBox; 
             if (firstRunForLayoutBox) {
                 // Setup display box for the associated layout box.
@@ -497,6 +497,9 @@
         }
         ASSERT_NOT_REACHED();
     }
+    // Make sure the trailing text run gets a hyphen when it needs one.
+    if (lineContent.partialContent && lineContent.partialContent->trailingContentNeedsHyphen)
+        inlineContent.runs[*lastTextItemIndex].textContext()->setNeedsHyphen();
 }
 
 void InlineFormattingContext::invalidateFormattingState(const InvalidationState&)
diff --git a/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp b/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp
index 57ebafb..c84d1c1 100644
--- a/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp
+++ b/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp
@@ -72,8 +72,8 @@
 
     if (candidateRuns.hasTextContentOnly()) {
         auto& runs = candidateRuns.runs();
-        if (auto trailingPartialContent = wordBreakingBehavior(runs, lineStatus.availableWidth))
-            return { BreakingContext::ContentBreak::Split, trailingPartialContent };
+        if (auto partialTrailingContent = wordBreakingBehavior(runs, lineStatus.availableWidth))
+            return { BreakingContext::ContentBreak::Split, partialTrailingContent };
         // If we did not manage to break this content, we still need to decide whether keep it or wrap it to the next line.
         // FIXME: Keep tracking the last breaking opportunity where we can wrap the content:
         // <span style="white-space: pre;">this fits</span> <span style="white-space: pre;">this does not fit but does not wrap either</span>
@@ -95,7 +95,7 @@
     return !lineIsEmpty && floatLogicalWidth > availableWidth;
 }
 
-Optional<LineBreaker::BreakingContext::TrailingPartialContent> LineBreaker::wordBreakingBehavior(const Content::RunList& runs, LayoutUnit availableWidth) const
+Optional<LineBreaker::BreakingContext::PartialTrailingContent> LineBreaker::wordBreakingBehavior(const Content::RunList& runs, LayoutUnit availableWidth) const
 {
     // Check where the overflow occurs and use the corresponding style to figure out the breaking behaviour.
     // <span style="word-break: normal">first</span><span style="word-break: break-all">second</span><span style="word-break: normal">third</span>
@@ -122,7 +122,7 @@
         // When the first span computes longer than the available space, by the time we get to the second span, the adjusted available space becomes negative.
         auto adjustedAvailableWidth = std::max(LayoutUnit { }, availableWidth - runsWidth + run.logicalWidth);
         if (auto leftSide = tryBreakingTextRun(run, adjustedAvailableWidth))
-            return BreakingContext::TrailingPartialContent { i, leftSide->length, leftSide->logicalWidth, leftSide->hasHyphen };
+            return BreakingContext::PartialTrailingContent { i, leftSide->length, leftSide->logicalWidth, leftSide->needsHyphen };
         return { };
     }
     // We did not manage to break in this sequence of runs.
diff --git a/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h b/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h
index 852ac0c..0d523ae 100644
--- a/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h
+++ b/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h
@@ -40,13 +40,13 @@
     struct BreakingContext {
         enum class ContentBreak { Keep, Split, Wrap };
         ContentBreak contentBreak;
-        struct TrailingPartialContent {
+        struct PartialTrailingContent {
             unsigned runIndex { 0 };
             unsigned length { 0 };
             LayoutUnit logicalWidth;
-            bool hasHyphen { false };
+            bool needsHyphen { false };
         };
-        Optional<TrailingPartialContent> trailingPartialContent;
+        Optional<PartialTrailingContent> partialTrailingContent;
     };
 
     // This struct represents the amount of content committed to line breaking at a time e.g.
@@ -107,11 +107,11 @@
     void setHyphenationDisabled() { n_hyphenationIsDisabled = true; }
 
 private:
-    Optional<BreakingContext::TrailingPartialContent> wordBreakingBehavior(const Content::RunList&, LayoutUnit availableWidth) const;
+    Optional<BreakingContext::PartialTrailingContent> wordBreakingBehavior(const Content::RunList&, LayoutUnit availableWidth) const;
     struct LeftSide {
         unsigned length { 0 };
         LayoutUnit logicalWidth;
-        bool hasHyphen { false };
+        bool needsHyphen { false };
     };
     Optional<LeftSide> tryBreakingTextRun(const Content::Run& overflowRun, LayoutUnit availableWidth) const;
 
diff --git a/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp b/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp
index 22ad074..3d451ab 100644
--- a/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp
+++ b/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp
@@ -77,27 +77,27 @@
 {
 }
 
-LineLayoutContext::LineContent LineLayoutContext::layoutLine(LineBuilder& line, unsigned leadingInlineItemIndex, Optional<PartialContent> leadingPartialContent)
+LineLayoutContext::LineContent LineLayoutContext::layoutLine(LineBuilder& line, unsigned leadingInlineItemIndex, Optional<unsigned> partialLeadingContentLength)
 {
     auto initialize = [&] {
         m_committedInlineItemCount = 0;
         m_uncommittedContent.reset();
-        m_leadingPartialTextItem = { };
-        m_trailingPartialTextItem = { };
-        m_overflowPartialContent = { };
+        m_partialLeadingTextItem = { };
+        m_partialTrailingTextItem = { };
+        m_partialContent = { };
     };
     initialize();
     // Iterate through the inline content and place the inline boxes on the current line.
     // Start with the partial leading text from the previous line.
     auto firstNonPartialInlineItemIndex = leadingInlineItemIndex;
-    if (leadingPartialContent) {
+    if (partialLeadingContentLength) {
         // Handle partial inline item (split text from the previous line).
         auto& leadingTextItem = m_inlineItems[leadingInlineItemIndex];
         RELEASE_ASSERT(leadingTextItem->isText());
         // Construct a partial leading inline item.
-        ASSERT(!m_leadingPartialTextItem);
-        m_leadingPartialTextItem = downcast<InlineTextItem>(*leadingTextItem).right(leadingPartialContent->length);
-        if (placeInlineItem(line, *m_leadingPartialTextItem) == IsEndOfLine::Yes)
+        ASSERT(!m_partialLeadingTextItem);
+        m_partialLeadingTextItem = downcast<InlineTextItem>(*leadingTextItem).right(*partialLeadingContentLength);
+        if (placeInlineItem(line, *m_partialLeadingTextItem) == IsEndOfLine::Yes)
             return close(line, leadingInlineItemIndex);
         ++firstNonPartialInlineItemIndex;
     }
@@ -132,7 +132,7 @@
 
     auto trailingInlineItemIndex = leadingInlineItemIndex + m_committedInlineItemCount - 1;
     auto isLastLineWithInlineContent = [&] {
-        if (m_overflowPartialContent)
+        if (m_partialContent)
             return LineBuilder::IsLastLineWithInlineContent::No;
         // Skip floats backwards to see if this is going to be the last line with inline content.
         for (auto i = m_inlineItems.size(); i--;) {
@@ -144,7 +144,7 @@
         return LineBuilder::IsLastLineWithInlineContent::No;
     };
 
-    return LineContent { trailingInlineItemIndex, m_overflowPartialContent, WTFMove(m_floats), line.close(isLastLineWithInlineContent()), line.lineBox() };
+    return LineContent { trailingInlineItemIndex, m_partialContent, WTFMove(m_floats), line.close(isLastLineWithInlineContent()), line.lineBox() };
 }
 
 LineLayoutContext::IsEndOfLine LineLayoutContext::placeInlineItem(LineBuilder& line, const InlineItem& inlineItem)
@@ -215,27 +215,27 @@
     if (breakingContext.contentBreak == LineBreaker::BreakingContext::ContentBreak::Keep)
         commitPendingContent(line);
     else if (breakingContext.contentBreak == LineBreaker::BreakingContext::ContentBreak::Split) {
-        ASSERT(breakingContext.trailingPartialContent);
-        ASSERT(m_uncommittedContent.runs()[breakingContext.trailingPartialContent->runIndex].inlineItem.isText());
+        ASSERT(breakingContext.partialTrailingContent);
+        ASSERT(m_uncommittedContent.runs()[breakingContext.partialTrailingContent->runIndex].inlineItem.isText());
         // Turn the uncommitted trailing run into a partial trailing run.
-        auto overflowInlineTextItemIndex = breakingContext.trailingPartialContent->runIndex;
+        auto overflowInlineTextItemIndex = breakingContext.partialTrailingContent->runIndex;
         auto& overflowInlineTextItem = downcast<InlineTextItem>(m_uncommittedContent.runs()[overflowInlineTextItemIndex].inlineItem);
 
         // Construct a partial trailing inline run.
-        ASSERT(!m_trailingPartialTextItem);
-        auto trailingContentLength = breakingContext.trailingPartialContent->length;
-        m_trailingPartialTextItem = overflowInlineTextItem.left(trailingContentLength);
-        m_overflowPartialContent = PartialContent { overflowInlineTextItem.length() - trailingContentLength };
+        ASSERT(!m_partialTrailingTextItem);
+        auto trailingContentLength = breakingContext.partialTrailingContent->length;
+        m_partialTrailingTextItem = overflowInlineTextItem.left(trailingContentLength);
+        m_partialContent = LineContent::PartialContent { breakingContext.partialTrailingContent->needsHyphen, overflowInlineTextItem.length() - trailingContentLength };
         // Keep the non-overflow part of the uncommitted runs and add the trailing partial content.
         m_uncommittedContent.trim(overflowInlineTextItemIndex);
-        m_uncommittedContent.append(*m_trailingPartialTextItem, breakingContext.trailingPartialContent->logicalWidth);
+        m_uncommittedContent.append(*m_partialTrailingTextItem, breakingContext.partialTrailingContent->logicalWidth);
         commitPendingContent(line);
     } else if (breakingContext.contentBreak == LineBreaker::BreakingContext::ContentBreak::Wrap)
         m_uncommittedContent.reset();
     else
         ASSERT_NOT_REACHED();
     // Adjust hyphenated line count
-    m_successiveHyphenatedLineCount = breakingContext.trailingPartialContent && breakingContext.trailingPartialContent->hasHyphen ? m_successiveHyphenatedLineCount + 1 : 0;
+    m_successiveHyphenatedLineCount = breakingContext.partialTrailingContent && breakingContext.partialTrailingContent->needsHyphen ? m_successiveHyphenatedLineCount + 1 : 0;
     return breakingContext.contentBreak == LineBreaker::BreakingContext::ContentBreak::Keep ? IsEndOfLine::No :IsEndOfLine::Yes;
 }
 
diff --git a/Source/WebCore/layout/inlineformatting/LineLayoutContext.h b/Source/WebCore/layout/inlineformatting/LineLayoutContext.h
index 62a5139..0fb80b6 100644
--- a/Source/WebCore/layout/inlineformatting/LineLayoutContext.h
+++ b/Source/WebCore/layout/inlineformatting/LineLayoutContext.h
@@ -37,18 +37,18 @@
 public:
     LineLayoutContext(const InlineFormattingContext&, const Container& formattingContextRoot, const InlineItems&);
 
-    struct PartialContent {
-        // This will potentially gain some more members. 
-        unsigned length { 0 };
-    };
     struct LineContent {
+        struct PartialContent {
+            bool trailingContentNeedsHyphen { false };
+            unsigned overlfowContentLength { 0 };
+        };
         Optional<unsigned> trailingInlineItemIndex;
-        Optional<PartialContent> overflowPartialContent;
+        Optional<PartialContent> partialContent;
         Vector<WeakPtr<InlineItem>> floats;
         const LineBuilder::RunList runList;
         const LineBox lineBox;
     };
-    LineContent layoutLine(LineBuilder&, unsigned leadingInlineItemIndex, Optional<PartialContent> leadingPartialContent);
+    LineContent layoutLine(LineBuilder&, unsigned leadingInlineItemIndex, Optional<unsigned> partialLeadingContentLength);
 
 private:
     const InlineFormattingContext& formattingContext() const { return m_inlineFormattingContext; }
@@ -65,9 +65,9 @@
     LineBreaker::Content m_uncommittedContent;
     unsigned m_committedInlineItemCount { 0 };
     Vector<WeakPtr<InlineItem>> m_floats;
-    std::unique_ptr<InlineTextItem> m_leadingPartialTextItem;
-    std::unique_ptr<InlineTextItem> m_trailingPartialTextItem;
-    Optional<PartialContent> m_overflowPartialContent;
+    std::unique_ptr<InlineTextItem> m_partialLeadingTextItem;
+    std::unique_ptr<InlineTextItem> m_partialTrailingTextItem;
+    Optional<LineContent::PartialContent> m_partialContent;
     unsigned m_successiveHyphenatedLineCount { 0 };
 };
 
diff --git a/Source/WebCore/layout/integration/LayoutIntegrationLineLayout.cpp b/Source/WebCore/layout/integration/LayoutIntegrationLineLayout.cpp
index 90d098d..3b5f380 100644
--- a/Source/WebCore/layout/integration/LayoutIntegrationLineLayout.cpp
+++ b/Source/WebCore/layout/integration/LayoutIntegrationLineLayout.cpp
@@ -212,8 +212,6 @@
             continue;
         }
 
-        // FIXME: Hyphens.
-
         auto& lineBox = inlineContent.lineBoxForRun(run);
         auto baselineOffset = paintOffset.y() + lineBox.logicalTop() + lineBox.baselineOffset();
 
@@ -221,7 +219,10 @@
         auto horizontalExpansion = textContext.expansion() ? textContext.expansion()->horizontalExpansion : 0_lu;
         auto logicalLeft = paintOffset.x() + run.logicalLeft();
 
-        TextRun textRun { textContext.content(), logicalLeft, horizontalExpansion, behavior };
+        String textWithHyphen;
+        if (textContext.needsHyphen())
+            textWithHyphen = makeString(textContext.content(), style.hyphenString());
+        TextRun textRun { !textWithHyphen.isEmpty() ? textWithHyphen : textContext.content(), logicalLeft, horizontalExpansion, behavior };
         textRun.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
         FloatPoint textOrigin { rect.x() + paintOffset.x(), roundToDevicePixel(baselineOffset, deviceScaleFactor) };