[LFC][IFC] Add center/right/justify line alignment support.
authorzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 22 Jul 2018 19:48:21 +0000 (19:48 +0000)
committerzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 22 Jul 2018 19:48:21 +0000 (19:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=187890

Reviewed by Antti Koivisto.

Move over some more code from simple line layout.
(though text-align: justify is more preformant as now expansion opportunities are added as we process the text runs
-as opposed to iterting through the runs again when we reach the end of the line.)

* layout/inlineformatting/textlayout/Runs.h:
(WebCore::Layout::LayoutRun::setLeft):
(WebCore::Layout::LayoutRun::setExpansion):
* layout/inlineformatting/textlayout/simple/SimpleLineBreaker.cpp:
(WebCore::Layout::SimpleLineBreaker::Line::Line):
(WebCore::Layout::SimpleLineBreaker::Line::setTextAlign):
(WebCore::Layout::SimpleLineBreaker::Line::adjustedLeftForTextAlign const):
(WebCore::Layout::SimpleLineBreaker::Line::justifyRuns):
(WebCore::Layout::SimpleLineBreaker::Line::adjustRunsForTextAlign):
(WebCore::Layout::expansionOpportunity):
(WebCore::Layout::expansionBehavior):
(WebCore::Layout::SimpleLineBreaker::Line::collectExpansionOpportunities):
(WebCore::Layout::SimpleLineBreaker::Line::closeLastRun):
(WebCore::Layout::SimpleLineBreaker::Line::append):
(WebCore::Layout::SimpleLineBreaker::Line::collapseTrailingWhitespace):
(WebCore::Layout::SimpleLineBreaker::Line::reset):
(WebCore::Layout::SimpleLineBreaker::Style::Style):
(WebCore::Layout::SimpleLineBreaker::handleLineEnd):
(WebCore::Layout::SimpleLineBreaker::handleLineStart):
(WebCore::Layout::isTextAlignRight):
(WebCore::Layout::SimpleLineBreaker::createRunsForLine):
* layout/inlineformatting/textlayout/simple/SimpleLineBreaker.h:
(WebCore::Layout::SimpleLineBreaker::Line::setAvailableWidth):
(WebCore::Layout::SimpleLineBreaker::Line::setCollapseWhitespace):

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

Source/WebCore/ChangeLog
Source/WebCore/layout/inlineformatting/textlayout/Runs.h
Source/WebCore/layout/inlineformatting/textlayout/simple/SimpleLineBreaker.cpp
Source/WebCore/layout/inlineformatting/textlayout/simple/SimpleLineBreaker.h

index 2063d41..6cc423a 100644 (file)
@@ -1,3 +1,39 @@
+2018-07-22  Zalan Bujtas  <zalan@apple.com>
+
+        [LFC][IFC] Add center/right/justify line alignment support.
+        https://bugs.webkit.org/show_bug.cgi?id=187890
+
+        Reviewed by Antti Koivisto.
+
+        Move over some more code from simple line layout.
+        (though text-align: justify is more preformant as now expansion opportunities are added as we process the text runs
+        -as opposed to iterting through the runs again when we reach the end of the line.) 
+
+        * layout/inlineformatting/textlayout/Runs.h:
+        (WebCore::Layout::LayoutRun::setLeft):
+        (WebCore::Layout::LayoutRun::setExpansion):
+        * layout/inlineformatting/textlayout/simple/SimpleLineBreaker.cpp:
+        (WebCore::Layout::SimpleLineBreaker::Line::Line):
+        (WebCore::Layout::SimpleLineBreaker::Line::setTextAlign):
+        (WebCore::Layout::SimpleLineBreaker::Line::adjustedLeftForTextAlign const):
+        (WebCore::Layout::SimpleLineBreaker::Line::justifyRuns):
+        (WebCore::Layout::SimpleLineBreaker::Line::adjustRunsForTextAlign):
+        (WebCore::Layout::expansionOpportunity):
+        (WebCore::Layout::expansionBehavior):
+        (WebCore::Layout::SimpleLineBreaker::Line::collectExpansionOpportunities):
+        (WebCore::Layout::SimpleLineBreaker::Line::closeLastRun):
+        (WebCore::Layout::SimpleLineBreaker::Line::append):
+        (WebCore::Layout::SimpleLineBreaker::Line::collapseTrailingWhitespace):
+        (WebCore::Layout::SimpleLineBreaker::Line::reset):
+        (WebCore::Layout::SimpleLineBreaker::Style::Style):
+        (WebCore::Layout::SimpleLineBreaker::handleLineEnd):
+        (WebCore::Layout::SimpleLineBreaker::handleLineStart):
+        (WebCore::Layout::isTextAlignRight):
+        (WebCore::Layout::SimpleLineBreaker::createRunsForLine):
+        * layout/inlineformatting/textlayout/simple/SimpleLineBreaker.h:
+        (WebCore::Layout::SimpleLineBreaker::Line::setAvailableWidth):
+        (WebCore::Layout::SimpleLineBreaker::Line::setCollapseWhitespace):
+
 2018-07-21  Zalan Bujtas  <zalan@apple.com>
 
         [LFC][IFC] Add verification for inline text runs.
index d87fc10..4ea7433 100644 (file)
@@ -27,6 +27,8 @@
 
 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
 
+#include "TextFlags.h"
+
 namespace WebCore {
 namespace Layout {
 
@@ -91,15 +93,20 @@ public:
     bool isEndOfLine() const { return m_isEndOfLine; }
 
     void setEnd(ContentPosition end) { m_end = end; }
+    void setLeft(float left) { m_left = left; }
     void setRight(float right) { m_right = right; }
     void setIsEndOfLine() { m_isEndOfLine = true; }
 
+    void setExpansion(ExpansionBehavior, float expansion);
+
 private:
     ContentPosition m_start { 0 };
     ContentPosition m_end { 0 };
     float m_left { 0 };
     float m_right { 0 };
     bool m_isEndOfLine { false };
+    float m_expansion { 0 };
+    ExpansionBehavior m_expansionBehavior { ForbidLeadingExpansion | ForbidTrailingExpansion };
 };
 
 template<typename T>
@@ -145,6 +152,12 @@ inline LayoutRun::LayoutRun(ContentPosition start, ContentPosition end, float le
 {
 }
 
+inline void LayoutRun::setExpansion(ExpansionBehavior expansionBehavior, float expansion)
+{
+    m_expansionBehavior = expansionBehavior;
+    m_expansion = expansion;
+}
+
 inline TextRun TextRun::createWhitespaceRun(ContentPosition start, ContentPosition end, float width, bool isCollapsed)
 {
     return { start, end, Type::Whitespace, width, isCollapsed };
index fcf572c..466e4b0 100644 (file)
@@ -31,7 +31,6 @@
 #include "FontCascade.h"
 #include "InlineFormattingContext.h"
 #include "LayoutContext.h"
-#include "LayoutUnit.h"
 #include "RenderStyle.h"
 #include "TextContentProvider.h"
 #include <wtf/IsoMallocInlines.h>
@@ -53,6 +52,7 @@ SimpleLineBreaker::TextRunList::TextRunList(const Vector<TextRun>& textRuns)
 
 SimpleLineBreaker::Line::Line(Vector<LayoutRun>& layoutRuns)
     : m_layoutRuns(layoutRuns)
+    , m_firstRunIndex(m_layoutRuns.size())
 {
 }
 
@@ -63,11 +63,145 @@ static inline unsigned adjustedEndPosition(const TextRun& textRun)
     return textRun.end();
 }
 
+void SimpleLineBreaker::Line::setTextAlign(TextAlignMode textAlign)
+{
+    m_style.textAlign = textAlign;
+    m_collectExpansionOpportunities = textAlign == TextAlignMode::Justify; 
+}
+
+float SimpleLineBreaker::Line::adjustedLeftForTextAlign(TextAlignMode textAlign) const
+{
+    float remainingWidth = availableWidth();
+    float left = m_left;
+    switch (textAlign) {
+    case TextAlignMode::Left:
+    case TextAlignMode::WebKitLeft:
+    case TextAlignMode::Start:
+        return left;
+    case TextAlignMode::Right:
+    case TextAlignMode::WebKitRight:
+    case TextAlignMode::End:
+        return left + std::max<float>(remainingWidth, 0);
+    case TextAlignMode::Center:
+    case TextAlignMode::WebKitCenter:
+        return left + std::max<float>(remainingWidth / 2, 0);
+    case TextAlignMode::Justify:
+        ASSERT_NOT_REACHED();
+        break;
+    }
+    ASSERT_NOT_REACHED();
+    return left;
+}
+
+void SimpleLineBreaker::Line::justifyRuns()
+{
+    auto widthToDistribute = availableWidth();
+    if (widthToDistribute <= 0)
+        return;
+
+    unsigned expansionOpportunities = 0;
+    for (auto& expansionEntry : m_expansionOpportunityList)
+        expansionOpportunities += expansionEntry.count;
+
+    if (!expansionOpportunities)
+        return;
+
+    auto expansion = widthToDistribute / expansionOpportunities;
+    float accumulatedExpansion = 0;
+    unsigned runIndex = m_firstRunIndex;
+    for (auto& expansionEntry : m_expansionOpportunityList) {
+        if (runIndex >= m_layoutRuns.size()) {
+            ASSERT_NOT_REACHED();
+            return;
+        }
+        auto& layoutRun = m_layoutRuns.at(runIndex++);
+        auto expansionForRun = expansionEntry.count * expansion; 
+
+        layoutRun.setExpansion(expansionEntry.behavior, expansionForRun);
+        layoutRun.setLeft(layoutRun.left() + accumulatedExpansion);
+        layoutRun.setRight(layoutRun.right() + accumulatedExpansion + expansionForRun);
+        accumulatedExpansion += expansionForRun;
+    }
+}
+
+void SimpleLineBreaker::Line::adjustRunsForTextAlign(bool lastLine)
+{
+    // Fallback to TextAlignMode::Left (START) alignment for non-collapsable content or for the last line before a forced break/the end of the block.
+    auto textAlign = m_style.textAlign;
+    if (textAlign == TextAlignMode::Justify && (!m_style.collapseWhitespace || lastLine))
+        textAlign = TextAlignMode::Left;
+
+    if (textAlign == TextAlignMode::Justify) {
+        justifyRuns();
+        return;
+    }
+    auto adjustedLeft = adjustedLeftForTextAlign(textAlign);
+    if (adjustedLeft == m_left)
+        return;
+
+    for (auto i = m_firstRunIndex; i < m_layoutRuns.size(); ++i) {
+        auto& layoutRun = m_layoutRuns.at(i);
+        layoutRun.setLeft(layoutRun.left() + adjustedLeft);
+        layoutRun.setRight(layoutRun.right() + adjustedLeft);
+    }
+}
+
+static bool expansionOpportunity(TextRun::Type current, TextRun::Type previous)
+{
+    return current == TextRun::Type::Whitespace || (current == TextRun::Type::NonWhitespace && previous == TextRun::Type::NonWhitespace);
+}
+
+static ExpansionBehavior expansionBehavior(bool isAtExpansionOpportunity)
+{
+    ExpansionBehavior expansionBehavior = AllowTrailingExpansion;
+    expansionBehavior |= isAtExpansionOpportunity ? ForbidLeadingExpansion : AllowLeadingExpansion;
+    return expansionBehavior;
+}
+
+void SimpleLineBreaker::Line::collectExpansionOpportunities(const TextRun& textRun, bool textRunCreatesNewLayoutRun)
+{
+    if (textRunCreatesNewLayoutRun) {
+        // Create an entry for this new layout run.
+        m_expansionOpportunityList.append({ });
+    }
+    
+    if (!textRun.length())
+        return;
+
+    auto isAtExpansionOpportunity = expansionOpportunity(textRun.type(), m_lastTextRun ? m_lastTextRun->type() : TextRun::Type::Invalid);
+    m_expansionOpportunityList.last().behavior = expansionBehavior(isAtExpansionOpportunity);
+    if (isAtExpansionOpportunity)
+        ++m_expansionOpportunityList.last().count;
+
+    if (textRun.isNonWhitespace())
+        m_lastNonWhitespaceExpansionOppportunity = m_expansionOpportunityList.last(); 
+}
+
+void SimpleLineBreaker::Line::closeLastRun()
+{
+    if (!m_layoutRuns.size())
+        return;
+
+    m_layoutRuns.last().setIsEndOfLine();
+    
+    // Forbid trailing expansion for the last run on line.
+    if (!m_collectExpansionOpportunities || m_expansionOpportunityList.isEmpty())
+        return;
+    
+    auto& lastExpansionEntry = m_expansionOpportunityList.last(); 
+    auto expansionBehavior = lastExpansionEntry.behavior;
+    // Remove allow and add forbid.
+    expansionBehavior ^= AllowTrailingExpansion;
+    expansionBehavior |= ForbidTrailingExpansion;
+    lastExpansionEntry.behavior = expansionBehavior;
+}
+
 void SimpleLineBreaker::Line::append(const TextRun& textRun)
 {
     auto start = textRun.start();
     auto end = adjustedEndPosition(textRun);
     auto previousLogicalRight = m_left + m_runsWidth;
+    bool textRunCreatesNewLayoutRun = !m_lastTextRun || m_lastTextRun->isCollapsed();
 
     m_runsWidth += textRun.width();
     if (textRun.isNonWhitespace()) {
@@ -76,8 +210,11 @@ void SimpleLineBreaker::Line::append(const TextRun& textRun)
     } else if (textRun.isWhitespace())
         m_trailingWhitespaceWidth += textRun.width();
 
+    if (m_collectExpansionOpportunities)
+        collectExpansionOpportunities(textRun, textRunCreatesNewLayoutRun);
+
     // New line needs new run.
-    if (!m_lastTextRun || m_lastTextRun->isCollapsed())
+    if (textRunCreatesNewLayoutRun)
         m_layoutRuns.append({ start, end, previousLogicalRight, previousLogicalRight + textRun.width() });
     else {
         auto& lastRun = m_layoutRuns.last();
@@ -94,6 +231,7 @@ void SimpleLineBreaker::Line::collapseTrailingWhitespace()
         return;
 
     if (!m_lastNonWhitespaceTextRun) {
+        // This essentially becomes an empty line.
         reset();
         return;
     }
@@ -118,13 +256,22 @@ void SimpleLineBreaker::Line::collapseTrailingWhitespace()
         lastLayoutRun.setEnd(m_lastNonWhitespaceTextRun->end());
         m_trailingWhitespaceWidth = 0;
     }
+
+    if (m_collectExpansionOpportunities) {
+        ASSERT(m_lastNonWhitespaceExpansionOppportunity);
+        ASSERT(!m_expansionOpportunityList.isEmpty());
+        m_expansionOpportunityList.last() = *m_lastNonWhitespaceExpansionOppportunity;
+    }
 }
 
 void SimpleLineBreaker::Line::reset()
 {
     m_runsWidth = 0;
+    m_firstRunIndex = m_layoutRuns.size(); 
     m_availableWidth = 0;
     m_trailingWhitespaceWidth  = 0;
+    m_expansionOpportunityList.clear();
+    m_lastNonWhitespaceExpansionOppportunity = { };
     m_lastTextRun = std::nullopt;
     m_lastNonWhitespaceTextRun = std::nullopt;
 }
@@ -137,7 +284,7 @@ SimpleLineBreaker::Style::Style(const RenderStyle& style)
     , collapseWhitespace(style.collapseWhiteSpace())
     , preWrap(wrapLines && !collapseWhitespace)
     , preserveNewline(style.preserveNewline())
-    , textAlignIsRight(style.textAlign() == TextAlignMode::Right || style.textAlign() == TextAlignMode::WebKitRight)
+    , textAlign(style.textAlign())
 {
 }
 
@@ -167,10 +314,16 @@ Vector<LayoutRun> SimpleLineBreaker::runs()
 
 void SimpleLineBreaker::handleLineEnd()
 {
-    if (!m_layoutRuns.isEmpty())
-        m_layoutRuns.last().setIsEndOfLine();
-    ++m_numberOfLines;
-    m_previousLineHasNonForcedContent = m_currentLine.hasContent() && m_currentLine.availableWidth() >= 0;
+    auto lineHasContent = m_currentLine.hasContent(); 
+    if (lineHasContent) {
+        ASSERT(m_layoutRuns.size());
+        ++m_numberOfLines;
+        m_currentLine.closeLastRun();
+
+        auto lastLine = !m_textRunList.current(); 
+        m_currentLine.adjustRunsForTextAlign(lastLine);
+    }
+    m_previousLineHasNonForcedContent = lineHasContent && m_currentLine.availableWidth() >= 0;
     m_currentLine.reset();
 }
 
@@ -178,9 +331,16 @@ void SimpleLineBreaker::handleLineStart()
 {
     auto lineConstraint = this->lineConstraint(verticalPosition());
     m_currentLine.setLeft(lineConstraint.left);
+    m_currentLine.setTextAlign(m_style.textAlign);
+    m_currentLine.setCollapseWhitespace(m_style.collapseWhitespace);
     m_currentLine.setAvailableWidth(lineConstraint.right - lineConstraint.left);
 }
 
+static bool isTextAlignRight(TextAlignMode textAlign)
+{
+    return textAlign == TextAlignMode::Right || textAlign == TextAlignMode::WebKitRight;
+}
+
 void SimpleLineBreaker::createRunsForLine()
 {
     collapseLeadingWhitespace();
@@ -197,7 +357,7 @@ void SimpleLineBreaker::createRunsForLine()
         if (textRun->isLineBreak()) {
             // Add the new line only if there's nothing on the line. (otherwise the extra new line character would show up at the end of the content.)
             if (textRun->isHardLineBreak() || !m_currentLine.hasContent()) {
-                if (m_style.textAlignIsRight)
+                if (isTextAlignRight(m_style.textAlign))
                     m_currentLine.collapseTrailingWhitespace();
                 m_currentLine.append(*textRun);
             }
index e20d549..46435da 100644 (file)
 
 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
 
+#include "RenderStyleConstants.h"
 #include "Runs.h"
 #include <wtf/IsoMalloc.h>
 
 namespace WebCore {
 
-class LayoutUnit;
 class RenderStyle;
 
 namespace Layout {
@@ -68,7 +68,7 @@ private:
         bool collapseWhitespace { false };
         bool preWrap { false };
         bool preserveNewline { false };
-        bool textAlignIsRight { false };
+        TextAlignMode textAlign { TextAlignMode::Left };
     };
 
     class TextRunList {
@@ -96,21 +96,44 @@ private:
         bool hasContent() const { return m_runsWidth; }
 
         void reset();
-        void setAvailableWidth(float availableWidth) { m_availableWidth = availableWidth; }
         bool hasTrailingWhitespace() const { return m_trailingWhitespaceWidth; }
         bool isWhitespaceOnly() const { return m_runsWidth == m_trailingWhitespaceWidth; }
         void collapseTrailingWhitespace();
 
+        void setAvailableWidth(float availableWidth) { m_availableWidth = availableWidth; }
         void setLeft(float left) { m_left = left; }
+        void setTextAlign(TextAlignMode);
+        void setCollapseWhitespace(bool collapseWhitespace) { m_style.collapseWhitespace = collapseWhitespace; }
+
+        void closeLastRun();
+        void adjustRunsForTextAlign(bool lastLine);
 
     private:
+        struct Style {
+            bool collapseWhitespace { false };
+            TextAlignMode textAlign { TextAlignMode::Left };
+        };
+        float adjustedLeftForTextAlign(TextAlignMode) const;
+        void justifyRuns();
+        void collectExpansionOpportunities(const TextRun&, bool textRunCreatesNewLayoutRun);
+
         Vector<LayoutRun>& m_layoutRuns;
+        Style m_style;
+    
         float m_runsWidth { 0 };
         float m_availableWidth { 0 };
         float m_left { 0 };
         float m_trailingWhitespaceWidth { 0 };
+        unsigned m_firstRunIndex { 0 };
         std::optional<TextRun> m_lastTextRun;
         std::optional<TextRun> m_lastNonWhitespaceTextRun;
+        bool m_collectExpansionOpportunities { false };
+        struct ExpansionOpportunity {
+            unsigned count { 0 };
+            ExpansionBehavior behavior { DefaultExpansion };
+        };
+        Vector<ExpansionOpportunity> m_expansionOpportunityList;
+        std::optional<ExpansionOpportunity> m_lastNonWhitespaceExpansionOppportunity;
     };
 
     void handleLineStart();
@@ -131,8 +154,8 @@ private:
 
     TextRunList m_textRunList;
 
-    Line m_currentLine;
     Vector<LayoutRun> m_layoutRuns;
+    Line m_currentLine;
 
     LineConstraintList m_lineConstraintList;
     ConstVectorIterator<LineConstraint> m_lineConstraintIterator;