[LFC][IFC] Add support for hyphenate-limit-lines
authorzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 30 Nov 2019 15:16:22 +0000 (15:16 +0000)
committerzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 30 Nov 2019 15:16:22 +0000 (15:16 +0000)
https://bugs.webkit.org/show_bug.cgi?id=204712
<rdar://problem/57536727>

Reviewed by Antti Koivisto.

Source/WebCore:

Now IFC (hyphenation)feature matches SLL.

* layout/inlineformatting/InlineFormattingContext.cpp:
(WebCore::Layout::InlineFormattingContext::lineLayout):
(WebCore::Layout::InlineFormattingContext::computedIntrinsicWidthForConstraint const):
* layout/inlineformatting/InlineLineBreaker.cpp:
(WebCore::Layout::LineBreaker::wordBreakingBehavior const):
(WebCore::Layout::LineBreaker::tryBreakingTextRun const):
* layout/inlineformatting/InlineLineBreaker.h:
(WebCore::Layout::LineBreaker::setHyphenationDisabled):
* layout/inlineformatting/LineLayoutContext.cpp:
(WebCore::Layout::LineLayoutContext::LineLayoutContext):
(WebCore::Layout::LineLayoutContext::layoutLine):
(WebCore::Layout::LineLayoutContext::close):
(WebCore::Layout::LineLayoutContext::processUncommittedContent):
* layout/inlineformatting/LineLayoutContext.h:

Tools:

* LayoutReloaded/misc/LFC-passing-tests.txt:

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

Source/WebCore/ChangeLog
Source/WebCore/layout/inlineformatting/InlineFormattingContext.cpp
Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp
Source/WebCore/layout/inlineformatting/InlineLineBreaker.h
Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp
Source/WebCore/layout/inlineformatting/LineLayoutContext.h
Tools/ChangeLog
Tools/LayoutReloaded/misc/LFC-passing-tests.txt

index 436c461..3156ac2 100644 (file)
@@ -1,3 +1,28 @@
+2019-11-30  Zalan Bujtas  <zalan@apple.com>
+
+        [LFC][IFC] Add support for hyphenate-limit-lines
+        https://bugs.webkit.org/show_bug.cgi?id=204712
+        <rdar://problem/57536727>
+
+        Reviewed by Antti Koivisto.
+
+        Now IFC (hyphenation)feature matches SLL. 
+
+        * layout/inlineformatting/InlineFormattingContext.cpp:
+        (WebCore::Layout::InlineFormattingContext::lineLayout):
+        (WebCore::Layout::InlineFormattingContext::computedIntrinsicWidthForConstraint const):
+        * layout/inlineformatting/InlineLineBreaker.cpp:
+        (WebCore::Layout::LineBreaker::wordBreakingBehavior const):
+        (WebCore::Layout::LineBreaker::tryBreakingTextRun const):
+        * layout/inlineformatting/InlineLineBreaker.h:
+        (WebCore::Layout::LineBreaker::setHyphenationDisabled):
+        * layout/inlineformatting/LineLayoutContext.cpp:
+        (WebCore::Layout::LineLayoutContext::LineLayoutContext):
+        (WebCore::Layout::LineLayoutContext::layoutLine):
+        (WebCore::Layout::LineLayoutContext::close):
+        (WebCore::Layout::LineLayoutContext::processUncommittedContent):
+        * layout/inlineformatting/LineLayoutContext.h:
+
 2019-11-30  Antoine Quint  <graouts@apple.com>
 
         [Web Animations] Forward-filling animations should not schedule updates while filling
index 50976a0..65ab940 100644 (file)
@@ -96,7 +96,7 @@ void InlineFormattingContext::lineLayout(const UsedHorizontalValues& usedHorizon
     unsigned leadingInlineItemIndex = 0;
     Optional<LineLayoutContext::PartialContent> leadingPartialContent;
     auto lineBuilder = LineBuilder { *this, root().style().textAlign(), LineBuilder::SkipAlignment::No };
-    auto lineLayoutContext = LineLayoutContext { *this, inlineItems };
+    auto lineLayoutContext = LineLayoutContext { *this, root(), inlineItems };
 
     while (leadingInlineItemIndex < inlineItems.size()) {
         lineBuilder.initialize(constraintsForLine(usedHorizontalValues, lineLogicalTop));
@@ -107,11 +107,11 @@ void InlineFormattingContext::lineLayout(const UsedHorizontalValues& usedHorizon
         if (lineContent.trailingInlineItemIndex) {
             lineLogicalTop = lineContent.lineBox.logicalBottom();
             // When the trailing content is partial, we need to reuse the last InlinItem.
-            if (lineContent.trailingPartialContent) {
+            if (lineContent.overflowPartialContent) {
                 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.trailingPartialContent->length };
+                leadingPartialContent = LineLayoutContext::PartialContent { lineContent.overflowPartialContent->length };
             } else
                 leadingInlineItemIndex = *lineContent.trailingInlineItemIndex + 1;
         } else {
@@ -234,7 +234,7 @@ LayoutUnit InlineFormattingContext::computedIntrinsicWidthForConstraint(const Us
     LayoutUnit maximumLineWidth;
     unsigned leadingInlineItemIndex = 0;
     auto lineBuilder = LineBuilder { *this, root().style().textAlign(), LineBuilder::SkipAlignment::Yes };
-    auto lineLayoutContext = LineLayoutContext { *this, inlineItems };
+    auto lineLayoutContext = LineLayoutContext { *this, root(), inlineItems };
     while (leadingInlineItemIndex < inlineItems.size()) {
         // Only the horiztonal available width is constrained when computing intrinsic width.
         lineBuilder.initialize(LineBuilder::Constraints { { }, usedHorizontalValues.constraints.width, false, { } });
index 889aa0b..530b899 100644 (file)
@@ -121,14 +121,15 @@ Optional<LineBreaker::BreakingContext::TrailingPartialContent> LineBreaker::word
         // <span style="word-break: keep-all">textcontentwithnobreak</span><span>textcontentwithyesbreak</span>
         // 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 splitLengthAndWidth = tryBreakingTextRun(run, adjustedAvailableWidth))
-            return BreakingContext::TrailingPartialContent { i, splitLengthAndWidth->length, splitLengthAndWidth->leftLogicalWidth };
+        if (auto leftSide = tryBreakingTextRun(run, adjustedAvailableWidth))
+            return BreakingContext::TrailingPartialContent { i, leftSide->length, leftSide->logicalWidth, leftSide->hasHyphen };
+        return { };
     }
     // We did not manage to break in this sequence of runs.
     return { };
 }
 
-Optional<LineBreaker::SplitLengthAndWidth> LineBreaker::tryBreakingTextRun(const Content::Run& overflowRun, LayoutUnit availableWidth) const
+Optional<LineBreaker::LeftSide> LineBreaker::tryBreakingTextRun(const Content::Run& overflowRun, LayoutUnit availableWidth) const
 {
     ASSERT(overflowRun.inlineItem.isText());
     auto& style = overflowRun.inlineItem.style();
@@ -139,12 +140,12 @@ Optional<LineBreaker::SplitLengthAndWidth> LineBreaker::tryBreakingTextRun(const
     if (breakWords == WordBreak::BreakAll) {
         // FIXME: Pass in the content logical left to be able to measure tabs.
         auto splitData = TextUtil::split(inlineTextItem.layoutBox(), inlineTextItem.start(), inlineTextItem.length(), overflowRun.logicalWidth, availableWidth, { });
-        return SplitLengthAndWidth { splitData.length, splitData.logicalWidth };
+        return LeftSide { splitData.length, splitData.logicalWidth, false };
     }
     // Find the hyphen position as follows:
     // 1. Split the text by taking the hyphen width into account
     // 2. Find the last hyphen position before the split position
-    if (style.hyphens() != Hyphens::Auto || !canHyphenate(style.locale()))
+    if (n_hyphenationIsDisabled || style.hyphens() != Hyphens::Auto || !canHyphenate(style.locale()))
         return { };
 
     auto runLength = inlineTextItem.length();
@@ -173,7 +174,7 @@ Optional<LineBreaker::SplitLengthAndWidth> LineBreaker::tryBreakingTextRun(const
     unsigned hyphenLocation = lastHyphenLocation(StringView(textContent).substring(inlineTextItem.start(), inlineTextItem.length()), hyphenBefore, style.locale());
     if (!hyphenLocation || hyphenLocation < limitBefore)
         return { };
-    return SplitLengthAndWidth { hyphenLocation, TextUtil::width(inlineTextItem.layoutBox(), inlineTextItem.start(), hyphenLocation) };
+    return LeftSide { hyphenLocation, TextUtil::width(inlineTextItem.layoutBox(), inlineTextItem.start(), hyphenLocation), true };
 }
 
 bool LineBreaker::Content::isAtContentBoundary(const InlineItem& inlineItem, const Content& content)
index 1f72ab1..10cb87d 100644 (file)
@@ -44,6 +44,7 @@ public:
             unsigned runIndex { 0 };
             unsigned length { 0 };
             LayoutUnit logicalWidth;
+            bool hasHyphen { false };
         };
         Optional<TrailingPartialContent> trailingPartialContent;
     };
@@ -87,15 +88,18 @@ public:
     BreakingContext breakingContextForInlineContent(const Content&, LayoutUnit availableWidth, LayoutUnit trailingTrimmableWidth, bool lineIsEmpty);
     bool shouldWrapFloatBox(LayoutUnit floatLogicalWidth, LayoutUnit availableWidth, bool lineIsEmpty);
 
-private:
+    void setHyphenationDisabled() { n_hyphenationIsDisabled = true; }
 
+private:
     Optional<BreakingContext::TrailingPartialContent> wordBreakingBehavior(const Content::RunList&, LayoutUnit availableWidth) const;
-
-    struct SplitLengthAndWidth {
+    struct LeftSide {
         unsigned length { 0 };
-        LayoutUnit leftLogicalWidth;
+        LayoutUnit logicalWidth;
+        bool hasHyphen { false };
     };
-    Optional<SplitLengthAndWidth> tryBreakingTextRun(const Content::Run& overflowRun, LayoutUnit availableWidth) const;
+    Optional<LeftSide> tryBreakingTextRun(const Content::Run& overflowRun, LayoutUnit availableWidth) const;
+
+    bool n_hyphenationIsDisabled { false };
 };
 
 }
index 439c097..8596e32 100644 (file)
@@ -67,8 +67,9 @@ static LayoutUnit inlineItemWidth(const FormattingContext& formattingContext, co
     return boxGeometry.width();
 }
 
-LineLayoutContext::LineLayoutContext(const InlineFormattingContext& inlineFormattingContext, const InlineItems& inlineItems)
+LineLayoutContext::LineLayoutContext(const InlineFormattingContext& inlineFormattingContext, const Container& formattingContextRoot, const InlineItems& inlineItems)
     : m_inlineFormattingContext(inlineFormattingContext)
+    , m_formattingContextRoot(formattingContextRoot)
     , m_inlineItems(inlineItems)
 {
 }
@@ -80,7 +81,7 @@ LineLayoutContext::LineContent LineLayoutContext::layoutLine(LineBuilder& line,
         m_uncommittedContent.reset();
         m_leadingPartialTextItem = { };
         m_trailingPartialTextItem = { };
-        m_overflowTextLength = { };
+        m_overflowPartialContent = { };
     };
     initialize();
     // Iterate through the inline content and place the inline boxes on the current line.
@@ -126,13 +127,9 @@ LineLayoutContext::LineContent LineLayoutContext::close(LineBuilder& line, unsig
     if (!m_committedInlineItemCount)
         return LineContent { { }, { }, WTFMove(m_floats), line.close(), line.lineBox() };
 
-    Optional<PartialContent> overflowContent;
-    if (m_overflowTextLength)
-        overflowContent = PartialContent { *m_overflowTextLength };
     auto trailingInlineItemIndex = leadingInlineItemIndex + m_committedInlineItemCount - 1;
-
     auto isLastLineWithInlineContent = [&] {
-        if (overflowContent)
+        if (m_overflowPartialContent)
             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 +141,7 @@ LineLayoutContext::LineContent LineLayoutContext::close(LineBuilder& line, unsig
         return LineBuilder::IsLastLineWithInlineContent::No;
     };
 
-    return LineContent { trailingInlineItemIndex, overflowContent, WTFMove(m_floats), line.close(isLastLineWithInlineContent()), line.lineBox() };
+    return LineContent { trailingInlineItemIndex, m_overflowPartialContent, WTFMove(m_floats), line.close(isLastLineWithInlineContent()), line.lineBox() };
 }
 
 LineLayoutContext::IsEndOfLine LineLayoutContext::placeInlineItem(LineBuilder& line, const InlineItem& inlineItem)
@@ -198,9 +195,18 @@ LineLayoutContext::IsEndOfLine LineLayoutContext::placeInlineItem(LineBuilder& l
 
 LineLayoutContext::IsEndOfLine LineLayoutContext::processUncommittedContent(LineBuilder& line)
 {
+    auto shouldDisableHyphenation = [&] {
+        auto& style = m_formattingContextRoot.style();
+        unsigned limitLines = style.hyphenationLimitLines() == RenderStyle::initialHyphenationLimitLines() ? std::numeric_limits<unsigned>::max() : style.hyphenationLimitLines();
+        return m_successiveHyphenatedLineCount >= limitLines;
+    };
+
     // Check if the pending content fits.
     auto lineIsConsideredEmpty = line.isVisuallyEmpty() && !line.hasIntrusiveFloat();
-    auto breakingContext = LineBreaker().breakingContextForInlineContent(m_uncommittedContent, line.availableWidth(), line.trailingTrimmableWidth(), lineIsConsideredEmpty);
+    auto lineBreaker = LineBreaker { };
+    if (shouldDisableHyphenation())
+        lineBreaker.setHyphenationDisabled();
+    auto breakingContext = lineBreaker.breakingContextForInlineContent(m_uncommittedContent, line.availableWidth(), line.trailingTrimmableWidth(), lineIsConsideredEmpty);
     // The uncommitted content can fully, partially fit the current line (commit/partial commit) or not at all (reset).
     if (breakingContext.contentBreak == LineBreaker::BreakingContext::ContentBreak::Keep)
         commitPendingContent(line);
@@ -215,7 +221,7 @@ LineLayoutContext::IsEndOfLine LineLayoutContext::processUncommittedContent(Line
         ASSERT(!m_trailingPartialTextItem);
         auto trailingContentLength = breakingContext.trailingPartialContent->length;
         m_trailingPartialTextItem = overflowInlineTextItem.left(trailingContentLength);
-        m_overflowTextLength = overflowInlineTextItem.length() - trailingContentLength;
+        m_overflowPartialContent = PartialContent { 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);
@@ -224,6 +230,8 @@ LineLayoutContext::IsEndOfLine LineLayoutContext::processUncommittedContent(Line
         m_uncommittedContent.reset();
     else
         ASSERT_NOT_REACHED();
+    // Adjust hyphenated line count
+    m_successiveHyphenatedLineCount = breakingContext.trailingPartialContent && breakingContext.trailingPartialContent->hasHyphen ? m_successiveHyphenatedLineCount + 1 : 0;
     return breakingContext.contentBreak == LineBreaker::BreakingContext::ContentBreak::Keep ? IsEndOfLine::No :IsEndOfLine::Yes;
 }
 
index 09cf134..62a5139 100644 (file)
@@ -35,15 +35,15 @@ namespace Layout {
 
 class LineLayoutContext {
 public:
-    LineLayoutContext(const InlineFormattingContext&, const InlineItems&);
+    LineLayoutContext(const InlineFormattingContext&, const Container& formattingContextRoot, const InlineItems&);
 
     struct PartialContent {
         // This will potentially gain some more members. 
-        unsigned length;
+        unsigned length { 0 };
     };
     struct LineContent {
         Optional<unsigned> trailingInlineItemIndex;
-        Optional<PartialContent> trailingPartialContent;
+        Optional<PartialContent> overflowPartialContent;
         Vector<WeakPtr<InlineItem>> floats;
         const LineBuilder::RunList runList;
         const LineBox lineBox;
@@ -60,13 +60,15 @@ private:
     IsEndOfLine processUncommittedContent(LineBuilder&);
 
     const InlineFormattingContext& m_inlineFormattingContext;
+    const Container& m_formattingContextRoot;
     const InlineItems& m_inlineItems;
     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<unsigned> m_overflowTextLength;
+    Optional<PartialContent> m_overflowPartialContent;
+    unsigned m_successiveHyphenatedLineCount { 0 };
 };
 
 }
index 6a1ab8b..9398d0e 100644 (file)
@@ -1,3 +1,13 @@
+2019-11-30  Zalan Bujtas  <zalan@apple.com>
+
+        [LFC][IFC] Add support for hyphenate-limit-lines
+        https://bugs.webkit.org/show_bug.cgi?id=204712
+        <rdar://problem/57536727>
+
+        Reviewed by Antti Koivisto.
+
+        * LayoutReloaded/misc/LFC-passing-tests.txt:
+
 2019-11-30  Aakash Jain  <aakash_jain@apple.com>
 
         [EWS] Do not retry layout-tests build if the flaky test failures are also present in clean tree run
index 9e9f3eb..110bfb8 100644 (file)
@@ -842,6 +842,7 @@ fast/text/system-font-zero-size.html
 fast/text/system-ui-chinese-bold-fallback.html
 fast/text/simple-line-layout-hyphen-limit-after.html
 fast/text/simple-line-layout-hyphen-limit-before.html
+fast/text/simple-line-layout-hyphen-limit-lines.html
 fast/text/tab-with-kerning.html
 fast/text/text-combine-first-line-crash.html
 fast/text/text-combine-rendering.html