[LFC][IFC] Add justify text-align support.
authorzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 22 Oct 2018 16:27:32 +0000 (16:27 +0000)
committerzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 22 Oct 2018 16:27:32 +0000 (16:27 +0000)
https://bugs.webkit.org/show_bug.cgi?id=190779

Reviewed by Antti Koivisto.

Collect expansion opportunities and adjust runs accordingly.

* layout/inlineformatting/InlineFormattingContext.cpp:
(WebCore::Layout::InlineFormattingContext::layoutInlineContent const):
* layout/inlineformatting/InlineFormattingContext.h:
* layout/inlineformatting/InlineRun.h:
(WebCore::Layout::InlineRun::expansionOpportunity):
(WebCore::Layout::InlineRun::TextContext::setStart):
(WebCore::Layout::InlineRun::TextContext::setLength):
(WebCore::Layout::InlineRun::setTextContext):
(WebCore::Layout::InlineRun::createRun): Deleted.
(WebCore::Layout::InlineRun::createTextRun): Deleted.
* layout/inlineformatting/Line.cpp:
(WebCore::Layout::InlineFormattingContext::Line::Line):
(WebCore::Layout::InlineFormattingContext::Line::init):
(WebCore::Layout::InlineFormattingContext::Line::computeExpansionOpportunities):
(WebCore::Layout::InlineFormattingContext::Line::appendContent):
(WebCore::Layout::InlineFormattingContext::Line::justifyRuns):
(WebCore::Layout::InlineFormattingContext::Line::close):
(WebCore::Layout::isNonCollapsedText): Deleted.

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

Source/WebCore/ChangeLog
Source/WebCore/layout/inlineformatting/InlineFormattingContext.cpp
Source/WebCore/layout/inlineformatting/InlineFormattingContext.h
Source/WebCore/layout/inlineformatting/InlineRun.h
Source/WebCore/layout/inlineformatting/Line.cpp

index 09edd26..d108de7 100644 (file)
@@ -1,5 +1,33 @@
 2018-10-22  Zalan Bujtas  <zalan@apple.com>
 
+        [LFC][IFC] Add justify text-align support.
+        https://bugs.webkit.org/show_bug.cgi?id=190779
+
+        Reviewed by Antti Koivisto.
+
+        Collect expansion opportunities and adjust runs accordingly.
+
+        * layout/inlineformatting/InlineFormattingContext.cpp:
+        (WebCore::Layout::InlineFormattingContext::layoutInlineContent const):
+        * layout/inlineformatting/InlineFormattingContext.h:
+        * layout/inlineformatting/InlineRun.h:
+        (WebCore::Layout::InlineRun::expansionOpportunity):
+        (WebCore::Layout::InlineRun::TextContext::setStart):
+        (WebCore::Layout::InlineRun::TextContext::setLength):
+        (WebCore::Layout::InlineRun::setTextContext):
+        (WebCore::Layout::InlineRun::createRun): Deleted.
+        (WebCore::Layout::InlineRun::createTextRun): Deleted.
+        * layout/inlineformatting/Line.cpp:
+        (WebCore::Layout::InlineFormattingContext::Line::Line):
+        (WebCore::Layout::InlineFormattingContext::Line::init):
+        (WebCore::Layout::InlineFormattingContext::Line::computeExpansionOpportunities):
+        (WebCore::Layout::InlineFormattingContext::Line::appendContent):
+        (WebCore::Layout::InlineFormattingContext::Line::justifyRuns):
+        (WebCore::Layout::InlineFormattingContext::Line::close):
+        (WebCore::Layout::isNonCollapsedText): Deleted.
+
+2018-10-22  Zalan Bujtas  <zalan@apple.com>
+
         [LFC][IFC] Add (right, center)text-align support.
         https://bugs.webkit.org/show_bug.cgi?id=190745
 
index 81436d9..61b7a62 100644 (file)
@@ -143,7 +143,7 @@ void InlineFormattingContext::layoutInlineContent(const LayoutContext& layoutCon
         // This may or may not be the last run on line -but definitely not the first one.
         line.appendContent(*run);
     }
-    line.close();
+    line.close(Line::LastLine::Yes);
 }
 
 void InlineFormattingContext::computeWidthAndHeight(const LayoutContext& layoutContext, const Box& layoutBox) const
index 2d9fcf4..0683f1c 100644 (file)
@@ -53,19 +53,24 @@ private:
 
         void init(LayoutUnit lineLogicalLeft, LayoutUnit availableWidth);
         void appendContent(const InlineLineBreaker::Run&);
-        void close();
+        enum class LastLine { No, Yes };
+        void close(LastLine = LastLine::No);
 
         bool hasContent() const { return m_firstRunIndex.has_value(); }
         LayoutUnit contentLogicalRight();
         LayoutUnit availableWidth() const { return m_availableWidth; }
 
     private:
+        void justifyRuns();
+        void computeExpansionOpportunities(const InlineLineBreaker::Run&);
+
         struct TrailingTrimmableContent {
             LayoutUnit width;
             unsigned length;
         };
         std::optional<TrailingTrimmableContent> m_trailingTrimmableContent;
-        bool m_lastRunIsNotCollapsedText { true };
+        bool m_lastRunIsWhitespace { false };
+        bool m_lastRunCanExpand { false };
 
         InlineFormattingState& m_formattingState;
         const Box& m_formattingRoot;
@@ -75,6 +80,7 @@ private:
         LayoutUnit m_lineWidth;
 
         std::optional<unsigned> m_firstRunIndex;
+        bool m_alignmentIsJustify { false };
     };
 
     void layoutInlineContent(const LayoutContext&, InlineFormattingState&, const InlineRunProvider&) const;
index 280e68b..4aba427 100644 (file)
@@ -34,8 +34,7 @@ namespace WebCore {
 namespace Layout {
 
 struct InlineRun {
-    static InlineRun createRun(LayoutUnit logcialLeft, LayoutUnit width, const InlineItem&);
-    static InlineRun createTextRun(LayoutUnit logcialLeft, LayoutUnit width, ItemPosition, unsigned length, const InlineItem&);
+    InlineRun(LayoutUnit logcialLeft, LayoutUnit width, const InlineItem&);
 
     LayoutUnit logicalLeft() const { return m_logicalLeft; }
     LayoutUnit logicalRight() const { return logicalLeft() + width(); }
@@ -45,28 +44,36 @@ struct InlineRun {
     void setLogicalLeft(LayoutUnit logicalLeft) { m_logicalLeft = logicalLeft; }
     void setLogicalRight(LayoutUnit logicalRight) { m_width -= (this->logicalRight() - logicalRight); }
 
+    struct ExpansionOpportunity {
+        unsigned count { 0 };
+        ExpansionBehavior behavior { ForbidLeadingExpansion | ForbidTrailingExpansion };
+        LayoutUnit expansion;
+    };
+    ExpansionOpportunity& expansionOpportunity() { return m_expansionOpportunity; }
+
     struct TextContext {
     public:
         TextContext(ItemPosition, unsigned length);
 
+        void setStart(ItemPosition start) { m_start = start; }
+        void setLength(unsigned length) { m_length = length; }
+
         ItemPosition start() const { return m_start; }
         unsigned length() const { return m_length; }
 
-        void setLength(unsigned length) { m_length = length; }
-
     private:
         ItemPosition m_start;
         unsigned m_length;
     };
+    void setTextContext(TextContext textContext) { m_textContext.emplace(textContext); }
     std::optional<TextContext>& textContext() { return m_textContext; }
+
     const InlineItem& inlineItem() const { return m_inlineItem; }
 
 private:
-    InlineRun(LayoutUnit logcialLeft, LayoutUnit width, const InlineItem&);
-    InlineRun(LayoutUnit logcialLeft, LayoutUnit width, ItemPosition, unsigned length, const InlineItem&);
-
     LayoutUnit m_logicalLeft;
     LayoutUnit m_width;
+    ExpansionOpportunity m_expansionOpportunity;
 
     const InlineItem& m_inlineItem;
     std::optional<TextContext> m_textContext;
@@ -74,16 +81,6 @@ private:
 
 using InlineRuns = Vector<InlineRun>;
 
-inline InlineRun InlineRun::createRun(LayoutUnit logicalLeft, LayoutUnit width, const InlineItem& inlineItem)
-{
-    return { logicalLeft, width, inlineItem };
-}
-
-inline InlineRun InlineRun::createTextRun(LayoutUnit logicalLeft, LayoutUnit width, ItemPosition start, unsigned length, const InlineItem& inlineItem)
-{
-    return { logicalLeft, width, start, length, inlineItem };
-}
-
 inline InlineRun::InlineRun(LayoutUnit logicalLeft, LayoutUnit width, const InlineItem& inlineItem)
     : m_logicalLeft(logicalLeft)
     , m_width(width)
@@ -91,14 +88,6 @@ inline InlineRun::InlineRun(LayoutUnit logicalLeft, LayoutUnit width, const Inli
 {
 }
 
-inline InlineRun::InlineRun(LayoutUnit logicalLeft, LayoutUnit width, ItemPosition start, unsigned length, const InlineItem& inlineItem)
-    : m_logicalLeft(logicalLeft)
-    , m_width(width)
-    , m_inlineItem(inlineItem)
-    , m_textContext(TextContext { start, length })
-{
-}
-
 inline InlineRun::TextContext::TextContext(ItemPosition start, unsigned length)
     : m_start(start)
     , m_length(length)
index 11c89ab..70c19f8 100644 (file)
@@ -36,6 +36,7 @@ namespace Layout {
 InlineFormattingContext::Line::Line(InlineFormattingState& formattingState, const Box& formattingRoot)
     : m_formattingState(formattingState)
     , m_formattingRoot(formattingRoot)
+    , m_alignmentIsJustify(m_formattingRoot.style().textAlign() == TextAlignMode::Justify)
 {
 }
 
@@ -44,7 +45,11 @@ void InlineFormattingContext::Line::init(LayoutUnit lineLogicalLeft, LayoutUnit
     m_lineLogicalLeft = lineLogicalLeft;
     m_lineWidth = availableWidth;
     m_availableWidth = availableWidth;
+
     m_firstRunIndex = { };
+    m_lastRunIsWhitespace = false;
+    m_lastRunCanExpand = false;
+    m_trailingTrimmableContent = { };
 }
 
 static LayoutUnit adjustedLineLogicalLeft(TextAlignMode align, LayoutUnit lineLogicalLeft, LayoutUnit remainingWidth)
@@ -69,11 +74,6 @@ static LayoutUnit adjustedLineLogicalLeft(TextAlignMode align, LayoutUnit lineLo
     return lineLogicalLeft;
 }
 
-static bool isNonCollapsedText(const InlineRunProvider::Run& inlineRun)
-{
-    return inlineRun.isText() && !inlineRun.textContext()->isCollapsed();
-}
-
 static bool isTrimmableContent(const InlineRunProvider::Run& inlineRun)
 {
     return inlineRun.isWhitespace() && inlineRun.style().collapseWhiteSpace();
@@ -87,41 +87,109 @@ LayoutUnit InlineFormattingContext::Line::contentLogicalRight()
     return m_formattingState.inlineRuns().last().logicalRight();
 }
 
-void InlineFormattingContext::Line::appendContent(const InlineLineBreaker::Run& run)
+void InlineFormattingContext::Line::computeExpansionOpportunities(const InlineLineBreaker::Run& run)
 {
-    auto lastRunWasNotCollapsedText = m_lastRunIsNotCollapsedText;
-    m_trailingTrimmableContent = { };
-    m_availableWidth -= run.width;
+    if (!m_alignmentIsJustify)
+        return;
+
+    auto isExpansionOpportunity = [](auto currentRunIsWhitespace, auto lastRunIsWhitespace) {
+        return currentRunIsWhitespace || (!currentRunIsWhitespace && !lastRunIsWhitespace);
+    };
+
+    auto expansionBehavior = [](auto isAtExpansionOpportunity) {
+        ExpansionBehavior expansionBehavior = AllowTrailingExpansion;
+        expansionBehavior |= isAtExpansionOpportunity ? ForbidLeadingExpansion : AllowLeadingExpansion;
+        return expansionBehavior;
+    };
 
+    auto isAtExpansionOpportunity = isExpansionOpportunity(run.content.isWhitespace(), m_lastRunIsWhitespace);
+
+    auto& currentInlineRun = m_formattingState.inlineRuns().last();
+    auto& expansionOpportunity = currentInlineRun.expansionOpportunity();
+    if (isAtExpansionOpportunity)
+        ++expansionOpportunity.count;
+
+    expansionOpportunity.behavior = expansionBehavior(isAtExpansionOpportunity);
+}
+
+void InlineFormattingContext::Line::appendContent(const InlineLineBreaker::Run& run)
+{
     auto& content = run.content;
-    m_lastRunIsNotCollapsedText = isNonCollapsedText(content);
 
     // Append this text run to the end of the last text run, if the last run is continuous.
     std::optional<InlineRun::TextContext> textRun;
     if (content.isText()) {
         auto textContext = content.textContext();
         auto runLength = textContext->isCollapsed() ? 1 : textContext->length();
-
-        if (isTrimmableContent(content))
-            m_trailingTrimmableContent = TrailingTrimmableContent { run.width, runLength };
-
-        if (hasContent() && lastRunWasNotCollapsedText) {
-            auto& inlineRun = m_formattingState.inlineRuns().last();
-            inlineRun.setWidth(inlineRun.width() + run.width);
-            inlineRun.textContext()->setLength(inlineRun.textContext()->length() + runLength);
-            return;
-        }
         textRun = InlineRun::TextContext { textContext->start(), runLength };
     }
 
-    if (textRun)
-        m_formattingState.appendInlineRun(InlineRun::createTextRun(contentLogicalRight(), run.width, textRun->start(), textRun->length(), content.inlineItem()));
-    else
-        m_formattingState.appendInlineRun(InlineRun::createRun(contentLogicalRight(), run.width, content.inlineItem()));
+    auto requiresNewInlineRun = !hasContent() || !content.isText() || !m_lastRunCanExpand;
+    if (requiresNewInlineRun) {
+        auto inlineRun = InlineRun { contentLogicalRight(), run.width, content.inlineItem() };
+        if (textRun)
+            inlineRun.setTextContext({ textRun->start(), textRun->length() });
+        m_formattingState.appendInlineRun(inlineRun);
+    } else {
+        // Non-text runs always require new inline run.
+        ASSERT(textRun);
+        auto& inlineRun = m_formattingState.inlineRuns().last();
+        inlineRun.setWidth(inlineRun.width() + run.width);
+        inlineRun.textContext()->setLength(inlineRun.textContext()->length() + textRun->length());
+    }
+
+    computeExpansionOpportunities(run);
+
+    m_availableWidth -= run.width;
+    m_lastRunIsWhitespace = content.isWhitespace();
+    m_lastRunCanExpand = content.isText() && !content.textContext()->isCollapsed();
     m_firstRunIndex = m_firstRunIndex.value_or(m_formattingState.inlineRuns().size() - 1);
+    m_trailingTrimmableContent = { };
+    if (isTrimmableContent(content))
+        m_trailingTrimmableContent = TrailingTrimmableContent { run.width, textRun->length() };
+}
+
+void InlineFormattingContext::Line::justifyRuns()
+{
+    if (!hasContent())
+        return;
+
+    auto& inlineRuns = m_formattingState.inlineRuns();
+    auto& lastInlineRun = inlineRuns.last();
+
+    // Adjust (forbid) trailing expansion for the last text run on line.
+    auto expansionBehavior = lastInlineRun.expansionOpportunity().behavior;
+    // Remove allow and add forbid.
+    expansionBehavior ^= AllowTrailingExpansion;
+    expansionBehavior |= ForbidTrailingExpansion;
+    lastInlineRun.expansionOpportunity().behavior = expansionBehavior;
+
+    // Collect expansion opportunities and justify the runs.
+    auto widthToDistribute = availableWidth();
+    if (widthToDistribute <= 0)
+        return;
+
+    auto expansionOpportunities = 0;
+    for (auto runIndex = *m_firstRunIndex; runIndex < inlineRuns.size(); ++runIndex)
+        expansionOpportunities += inlineRuns[runIndex].expansionOpportunity().count;
+
+    if (!expansionOpportunities)
+        return;
+
+    float expansion = widthToDistribute.toFloat() / expansionOpportunities;
+    LayoutUnit accumulatedExpansion = 0;
+    for (auto runIndex = *m_firstRunIndex; runIndex < inlineRuns.size(); ++runIndex) {
+        auto& inlineRun = inlineRuns[runIndex];
+        auto expansionForRun = inlineRun.expansionOpportunity().count * expansion;
+
+        inlineRun.expansionOpportunity().expansion = expansionForRun;
+        inlineRun.setLogicalLeft(inlineRun.logicalLeft() + accumulatedExpansion);
+        inlineRun.setWidth(inlineRun.width() + expansionForRun);
+        accumulatedExpansion += expansionForRun;
+    }
 }
 
-void InlineFormattingContext::Line::close()
+void InlineFormattingContext::Line::close(LastLine isLastLine)
 {
     auto trimTrailingContent = [&]() {
 
@@ -133,22 +201,20 @@ void InlineFormattingContext::Line::close()
         lastInlineRun.textContext()->setLength(lastInlineRun.textContext()->length() - m_trailingTrimmableContent->length);
 
         if (!lastInlineRun.textContext()->length()) {
-            m_formattingState.inlineRuns().removeLast();
-            if (m_firstRunIndex.value())
-                --*m_firstRunIndex;
-            else
+            if (*m_firstRunIndex == m_formattingState.inlineRuns().size() - 1)
                 m_firstRunIndex = { };
+            m_formattingState.inlineRuns().removeLast();
         }
         m_availableWidth += m_trailingTrimmableContent->width;
         m_trailingTrimmableContent = { };
     };
 
-    auto alignRuns = [&]() {
+    auto alignRuns = [&](auto alignment) {
 
         if (!hasContent())
             return;
 
-        auto adjustedLogicalLeft = adjustedLineLogicalLeft(m_formattingRoot.style().textAlign(), m_lineLogicalLeft, m_availableWidth);
+        auto adjustedLogicalLeft = adjustedLineLogicalLeft(alignment, m_lineLogicalLeft, m_availableWidth);
         if (m_lineLogicalLeft == adjustedLogicalLeft)
             return;
 
@@ -162,7 +228,16 @@ void InlineFormattingContext::Line::close()
         return;
 
     trimTrailingContent();
-    alignRuns();
+
+    auto textAlignment = m_formattingRoot.style().textAlign();
+    if (m_alignmentIsJustify) {
+        if (isLastLine == LastLine::No) {
+            justifyRuns();
+            return;
+        }
+        textAlignment = TextAlignMode::Left;
+    }
+    alignRuns(textAlignment);
 }
 
 }