[LCF][IFC] Add support for hyphenation.
authorzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 23 Jul 2018 20:30:35 +0000 (20:30 +0000)
committerzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 23 Jul 2018 20:30:35 +0000 (20:30 +0000)
https://bugs.webkit.org/show_bug.cgi?id=187913

Reviewed by Antti Koivisto.

Move the hyphenation logic over from SimpleLineLayout::TextFragmentIterator.

* layout/inlineformatting/textlayout/Runs.h:
(WebCore::Layout::TextRun::hasHyphen const):
(WebCore::Layout::LayoutRun::setHasHyphen):
(WebCore::Layout::LayoutRun::hasHyphen const):
(WebCore::Layout::LayoutRun::LayoutRun):
(WebCore::Layout::TextRun::createNonWhitespaceRunWithHyphen):
(WebCore::Layout::TextRun::TextRun):
* layout/inlineformatting/textlayout/TextContentProvider.cpp:
(WebCore::Layout::TextContentProvider::findTextItemSlow const):
(WebCore::Layout::TextContentProvider::width const):
(WebCore::Layout::TextContentProvider::hyphenPositionBefore const):
* layout/inlineformatting/textlayout/TextContentProvider.h:
* layout/inlineformatting/textlayout/simple/SimpleLineBreaker.cpp:
(WebCore::Layout::SimpleLineBreaker::Line::setTextAlign):
(WebCore::Layout::SimpleLineBreaker::Line::justifyRuns):
(WebCore::Layout::SimpleLineBreaker::Line::adjustRunsForTextAlign):
(WebCore::Layout::SimpleLineBreaker::Line::collectExpansionOpportunities):
(WebCore::Layout::SimpleLineBreaker::Line::closeLastRun):
(WebCore::Layout::SimpleLineBreaker::Line::append):
(WebCore::Layout::SimpleLineBreaker::Line::reset):
(WebCore::Layout::SimpleLineBreaker::Style::Style):
(WebCore::Layout::SimpleLineBreaker::handleLineEnd):
(WebCore::Layout::SimpleLineBreaker::createRunsForLine):
(WebCore::Layout::SimpleLineBreaker::hyphenPositionBefore const):
(WebCore::Layout::SimpleLineBreaker::adjustSplitPositionWithHyphenation const):
(WebCore::Layout::SimpleLineBreaker::split const):
* layout/inlineformatting/textlayout/simple/SimpleLineBreaker.h:

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

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

index 5a147dc..82a4a9d 100644 (file)
@@ -1,3 +1,40 @@
+2018-07-23  Zalan Bujtas  <zalan@apple.com>
+
+        [LCF][IFC] Add support for hyphenation.
+        https://bugs.webkit.org/show_bug.cgi?id=187913
+
+        Reviewed by Antti Koivisto.
+
+        Move the hyphenation logic over from SimpleLineLayout::TextFragmentIterator.
+
+        * layout/inlineformatting/textlayout/Runs.h:
+        (WebCore::Layout::TextRun::hasHyphen const):
+        (WebCore::Layout::LayoutRun::setHasHyphen):
+        (WebCore::Layout::LayoutRun::hasHyphen const):
+        (WebCore::Layout::LayoutRun::LayoutRun):
+        (WebCore::Layout::TextRun::createNonWhitespaceRunWithHyphen):
+        (WebCore::Layout::TextRun::TextRun):
+        * layout/inlineformatting/textlayout/TextContentProvider.cpp:
+        (WebCore::Layout::TextContentProvider::findTextItemSlow const):
+        (WebCore::Layout::TextContentProvider::width const):
+        (WebCore::Layout::TextContentProvider::hyphenPositionBefore const):
+        * layout/inlineformatting/textlayout/TextContentProvider.h:
+        * layout/inlineformatting/textlayout/simple/SimpleLineBreaker.cpp:
+        (WebCore::Layout::SimpleLineBreaker::Line::setTextAlign):
+        (WebCore::Layout::SimpleLineBreaker::Line::justifyRuns):
+        (WebCore::Layout::SimpleLineBreaker::Line::adjustRunsForTextAlign):
+        (WebCore::Layout::SimpleLineBreaker::Line::collectExpansionOpportunities):
+        (WebCore::Layout::SimpleLineBreaker::Line::closeLastRun):
+        (WebCore::Layout::SimpleLineBreaker::Line::append):
+        (WebCore::Layout::SimpleLineBreaker::Line::reset):
+        (WebCore::Layout::SimpleLineBreaker::Style::Style):
+        (WebCore::Layout::SimpleLineBreaker::handleLineEnd):
+        (WebCore::Layout::SimpleLineBreaker::createRunsForLine):
+        (WebCore::Layout::SimpleLineBreaker::hyphenPositionBefore const):
+        (WebCore::Layout::SimpleLineBreaker::adjustSplitPositionWithHyphenation const):
+        (WebCore::Layout::SimpleLineBreaker::split const):
+        * layout/inlineformatting/textlayout/simple/SimpleLineBreaker.h:
+
 2018-07-23  Antoine Quint  <graouts@apple.com>
 
         [Web Animations] Querying the current time of a finished CSSAnimation after removing its target leads to a crash
index 4ea7433..9a4c1f2 100644 (file)
@@ -46,11 +46,12 @@ public:
     };
     static TextRun createWhitespaceRun(ContentPosition start, ContentPosition end, float width, bool isCollapsed);
     static TextRun createNonWhitespaceRun(ContentPosition start, ContentPosition end, float width);
+    static TextRun createNonWhitespaceRunWithHyphen(ContentPosition start, ContentPosition end, float width);
     static TextRun createSoftLineBreakRun(ContentPosition);
     static TextRun createHardLineBreakRun(ContentPosition);
 
     TextRun() = default;
-    TextRun(ContentPosition start, ContentPosition end, Type, float width = 0, bool isCollapsed = false);
+    TextRun(ContentPosition start, ContentPosition end, Type, float width = 0, bool isCollapsed = false, bool hasHyphen = false);
 
     ContentPosition start() const;
     ContentPosition end() const;
@@ -68,6 +69,7 @@ public:
     Type type() const { return m_type; }
 
     void setIsCollapsed(bool isCollapsed) { m_isCollapsed = isCollapsed; }
+    bool hasHyphen() const { return m_hasHyphen; }
     void setWidth(float width) { m_width = width; }
 
 private:
@@ -76,11 +78,12 @@ private:
     Type m_type { Type::Invalid };
     float m_width { 0 };
     bool m_isCollapsed { false };
+    bool m_hasHyphen { false };
 };
 
 struct LayoutRun {
 public:
-    LayoutRun(ContentPosition start, ContentPosition end, float left, float right);
+    LayoutRun(ContentPosition start, ContentPosition end, float left, float right, bool hasHyphen);
 
     ContentPosition start() const { return m_start; }
     ContentPosition end() const { return m_end; }
@@ -98,6 +101,8 @@ public:
     void setIsEndOfLine() { m_isEndOfLine = true; }
 
     void setExpansion(ExpansionBehavior, float expansion);
+    void setHasHyphen() { m_hasHyphen = true; }
+    bool hasHyphen() const { return m_hasHyphen; }
 
 private:
     ContentPosition m_start { 0 };
@@ -105,6 +110,7 @@ private:
     float m_left { 0 };
     float m_right { 0 };
     bool m_isEndOfLine { false };
+    bool m_hasHyphen { false };
     float m_expansion { 0 };
     ExpansionBehavior m_expansionBehavior { ForbidLeadingExpansion | ForbidTrailingExpansion };
 };
@@ -144,11 +150,12 @@ inline ConstVectorIterator<T>& ConstVectorIterator<T>::operator++()
     return *this;
 }
 
-inline LayoutRun::LayoutRun(ContentPosition start, ContentPosition end, float left, float right)
+inline LayoutRun::LayoutRun(ContentPosition start, ContentPosition end, float left, float right, bool hasHyphen)
     : m_start(start)
     , m_end(end)
     , m_left(left)
     , m_right(right)
+    , m_hasHyphen(hasHyphen)
 {
 }
 
@@ -168,6 +175,11 @@ inline TextRun TextRun::createNonWhitespaceRun(ContentPosition start, ContentPos
     return { start, end, Type::NonWhitespace, width };
 }
 
+inline TextRun TextRun::createNonWhitespaceRunWithHyphen(ContentPosition start, ContentPosition end, float width)
+{
+    return { start, end, Type::NonWhitespace, width, false, true };
+}
+
 inline TextRun TextRun::createSoftLineBreakRun(ContentPosition position)
 {
     return { position, position + 1, Type::SoftLineBreak };
@@ -178,12 +190,13 @@ inline TextRun TextRun::createHardLineBreakRun(ContentPosition position)
     return { position, position, Type::HardLineBreak };
 }
 
-inline TextRun::TextRun(ContentPosition start, ContentPosition end, Type type, float width, bool isCollapsed)
+inline TextRun::TextRun(ContentPosition start, ContentPosition end, Type type, float width, bool isCollapsed, bool hasHyphen)
     : m_start(start)
     , m_end(end)
     , m_type(type)
     , m_width(width)
     , m_isCollapsed(isCollapsed)
+    , m_hasHyphen(hasHyphen)
 {
 }
 
index e22bbbc..b7b0e7f 100644 (file)
@@ -29,6 +29,7 @@
 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
 
 #include "FontCascade.h"
+#include "Hyphenation.h"
 #include "RenderStyle.h"
 #include "SimpleTextRunGenerator.h"
 #include <wtf/IsoMallocInlines.h>
@@ -79,26 +80,27 @@ static bool contains(ContentPosition position, const TextContentProvider::TextIt
     return textItem.start <= position && position < textItem.end;
 }
 
-float TextContentProvider::width(ContentPosition from, ContentPosition to, float xPosition) const
+const TextContentProvider::TextItem* TextContentProvider::findTextItemSlow(ContentPosition position) const
 {
-    auto textItemPositionSlow = [&]() -> unsigned {
-        // Since this is an iterator like class, check the next item first (instead of starting the search from the beginning).
-        if (auto* textItem = (++m_textContentIterator).current()) {
-            if (contains(from, *textItem))
-                return textItem->start;
-        }
+    // Since this is an iterator like class, check the next item first (instead of starting the search from the beginning).
+    if (auto* textItem = (++m_textContentIterator).current()) {
+        if (contains(position, *textItem))
+            return textItem;
+    }
 
-        m_textContentIterator.reset();
-        while (auto* textItem = m_textContentIterator.current()) {
-            if (contains(from, *textItem))
-                return textItem->start;
-            ++m_textContentIterator;
-        }
+    m_textContentIterator.reset();
+    while (auto* textItem = m_textContentIterator.current()) {
+        if (contains(position, *textItem))
+            return textItem;
+        ++m_textContentIterator;
+    }
 
-        ASSERT_NOT_REACHED();
-        return 0;
-    };
+    ASSERT_NOT_REACHED();
+    return nullptr;
+}
 
+float TextContentProvider::width(ContentPosition from, ContentPosition to, float xPosition) const
+{
     if (from >= to) {
         ASSERT_NOT_REACHED();
         return 0;
@@ -113,7 +115,7 @@ float TextContentProvider::width(ContentPosition from, ContentPosition to, float
     float width = 0;
     auto length = to - from;
     auto* textItem = m_textContentIterator.current();
-    auto startPosition = from - (textItem && contains(from, *textItem) ? textItem->start : textItemPositionSlow());
+    auto startPosition = from - (textItem && contains(from, *textItem) ? textItem->start : findTextItemSlow(from)->start);
 
     while (length) {
         textItem = m_textContentIterator.current();
@@ -190,6 +192,30 @@ float TextContentProvider::fixedPitchWidth(String text, const TextItem::Style& s
     return width;
 }
 
+std::optional<ContentPosition> TextContentProvider::hyphenPositionBefore(ContentPosition from, ContentPosition to, ContentPosition before) const
+{
+    auto contentLength = length();
+    if (before >= contentLength || before < from || before > to) {
+        ASSERT_NOT_REACHED();
+        return { };
+    }
+
+    auto* textItem = m_textContentIterator.current();
+    if (!textItem || !contains(before, *textItem))
+        textItem = findTextItemSlow(before);
+
+    auto fromItemPosition = from - textItem->start;
+    auto stringForHyphenLocation = StringView(textItem->text).substring(fromItemPosition, to - from);
+
+    // adjustedBefore -> ContentPosition -> ItemPosition -> run position.
+    auto adjustedBefore = before - from;
+    auto hyphenLocation = lastHyphenLocation(stringForHyphenLocation, adjustedBefore, textItem->style.locale);
+    if (!hyphenLocation)
+        return { };
+
+    return from + hyphenLocation;
+}
+
 unsigned TextContentProvider::length() const
 {
     if (!m_textContent.size())
index a71543c..a16c3cf 100644 (file)
@@ -77,6 +77,7 @@ public:
 
     unsigned length() const;
     float width(ContentPosition from, ContentPosition to, float xPosition) const;
+    std::optional<ContentPosition> hyphenPositionBefore(ContentPosition from, ContentPosition to, ContentPosition before) const;
 
     class Iterator {
     public:
@@ -97,6 +98,7 @@ public:
 private:
     friend class Iterator;
 
+    const TextItem* findTextItemSlow(ContentPosition) const;
     float textWidth(const TextItem&, ItemPosition from, ItemPosition to, float xPosition) const;
     float fixedPitchWidth(String, const TextItem::Style&, ItemPosition from, ItemPosition to, float xPosition) const;
 
index 466e4b0..a2a74e7 100644 (file)
@@ -29,6 +29,7 @@
 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
 
 #include "FontCascade.h"
+#include "Hyphenation.h"
 #include "InlineFormattingContext.h"
 #include "LayoutContext.h"
 #include "RenderStyle.h"
@@ -66,7 +67,7 @@ static inline unsigned adjustedEndPosition(const TextRun& textRun)
 void SimpleLineBreaker::Line::setTextAlign(TextAlignMode textAlign)
 {
     m_style.textAlign = textAlign;
-    m_collectExpansionOpportunities = textAlign == TextAlignMode::Justify; 
+    m_collectExpansionOpportunities = textAlign == TextAlignMode::Justify;
 }
 
 float SimpleLineBreaker::Line::adjustedLeftForTextAlign(TextAlignMode textAlign) const
@@ -115,7 +116,7 @@ void SimpleLineBreaker::Line::justifyRuns()
             return;
         }
         auto& layoutRun = m_layoutRuns.at(runIndex++);
-        auto expansionForRun = expansionEntry.count * expansion; 
+        auto expansionForRun = expansionEntry.count * expansion;
 
         layoutRun.setExpansion(expansionEntry.behavior, expansionForRun);
         layoutRun.setLeft(layoutRun.left() + accumulatedExpansion);
@@ -135,6 +136,7 @@ void SimpleLineBreaker::Line::adjustRunsForTextAlign(bool lastLine)
         justifyRuns();
         return;
     }
+
     auto adjustedLeft = adjustedLeftForTextAlign(textAlign);
     if (adjustedLeft == m_left)
         return;
@@ -164,7 +166,7 @@ void SimpleLineBreaker::Line::collectExpansionOpportunities(const TextRun& textR
         // Create an entry for this new layout run.
         m_expansionOpportunityList.append({ });
     }
-    
+
     if (!textRun.length())
         return;
 
@@ -174,7 +176,7 @@ void SimpleLineBreaker::Line::collectExpansionOpportunities(const TextRun& textR
         ++m_expansionOpportunityList.last().count;
 
     if (textRun.isNonWhitespace())
-        m_lastNonWhitespaceExpansionOppportunity = m_expansionOpportunityList.last(); 
+        m_lastNonWhitespaceExpansionOppportunity = m_expansionOpportunityList.last();
 }
 
 void SimpleLineBreaker::Line::closeLastRun()
@@ -183,12 +185,12 @@ void SimpleLineBreaker::Line::closeLastRun()
         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& lastExpansionEntry = m_expansionOpportunityList.last();
     auto expansionBehavior = lastExpansionEntry.behavior;
     // Remove allow and add forbid.
     expansionBehavior ^= AllowTrailingExpansion;
@@ -215,11 +217,13 @@ void SimpleLineBreaker::Line::append(const TextRun& textRun)
 
     // New line needs new run.
     if (textRunCreatesNewLayoutRun)
-        m_layoutRuns.append({ start, end, previousLogicalRight, previousLogicalRight + textRun.width() });
+        m_layoutRuns.append({ start, end, previousLogicalRight, previousLogicalRight + textRun.width(), textRun.hasHyphen() });
     else {
         auto& lastRun = m_layoutRuns.last();
         lastRun.setEnd(end);
         lastRun.setRight(lastRun.right() + textRun.width());
+        if (textRun.hasHyphen())
+            lastRun.setHasHyphen();
     }
 
     m_lastTextRun = textRun;
@@ -267,7 +271,7 @@ void SimpleLineBreaker::Line::collapseTrailingWhitespace()
 void SimpleLineBreaker::Line::reset()
 {
     m_runsWidth = 0;
-    m_firstRunIndex = m_layoutRuns.size(); 
+    m_firstRunIndex = m_layoutRuns.size();
     m_availableWidth = 0;
     m_trailingWhitespaceWidth  = 0;
     m_expansionOpportunityList.clear();
@@ -278,14 +282,22 @@ void SimpleLineBreaker::Line::reset()
 
 // FIXME: Use variable style based on the current text run.
 SimpleLineBreaker::Style::Style(const RenderStyle& style)
-    : wrapLines(style.autoWrap())
+    : font(style.fontCascade())
+    , wrapLines(style.autoWrap())
     , breakAnyWordOnOverflow(style.wordBreak() == WordBreak::BreakAll && wrapLines)
     , breakFirstWordOnOverflow(breakAnyWordOnOverflow || (style.breakWords() && (wrapLines || style.preserveNewline())))
     , collapseWhitespace(style.collapseWhiteSpace())
     , preWrap(wrapLines && !collapseWhitespace)
     , preserveNewline(style.preserveNewline())
     , textAlign(style.textAlign())
+    , shouldHyphenate(style.hyphens() == Hyphens::Auto && canHyphenate(style.locale()))
+    , hyphenStringWidth(shouldHyphenate ? font.width(WebCore::TextRun(String(style.hyphenString()))) : 0)
+    , hyphenLimitBefore(style.hyphenationLimitBefore() < 0 ? 2 : style.hyphenationLimitBefore())
+    , hyphenLimitAfter(style.hyphenationLimitAfter() < 0 ? 2 : style.hyphenationLimitAfter())
+    , locale(style.locale())
 {
+    if (style.hyphenationLimitLines() > -1)
+        hyphenLimitLines = style.hyphenationLimitLines();
 }
 
 SimpleLineBreaker::SimpleLineBreaker(const Vector<TextRun>& textRuns, const TextContentProvider& contentProvider, LineConstraintList&& lineConstraintList, const RenderStyle& style)
@@ -314,15 +326,24 @@ Vector<LayoutRun> SimpleLineBreaker::runs()
 
 void SimpleLineBreaker::handleLineEnd()
 {
-    auto lineHasContent = m_currentLine.hasContent(); 
+    auto lineHasContent = m_currentLine.hasContent();
     if (lineHasContent) {
         ASSERT(m_layoutRuns.size());
         ++m_numberOfLines;
         m_currentLine.closeLastRun();
 
-        auto lastLine = !m_textRunList.current(); 
+        auto lastLine = !m_textRunList.current();
         m_currentLine.adjustRunsForTextAlign(lastLine);
     }
+    // Check if we need to disable hyphenation.
+    if (m_style.hyphenLimitLines) {
+        if (!lineHasContent || (m_layoutRuns.size() && !m_layoutRuns.last().hasHyphen()))
+            m_numberOfPrecedingLinesWithHyphen = 0;
+        else
+            ++m_numberOfPrecedingLinesWithHyphen;
+        m_hyphenationIsDisabled = m_numberOfPrecedingLinesWithHyphen >= *m_style.hyphenLimitLines;
+    }
+
     m_previousLineHasNonForcedContent = lineHasContent && m_currentLine.availableWidth() >= 0;
     m_currentLine.reset();
 }
@@ -391,6 +412,14 @@ void SimpleLineBreaker::createRunsForLine()
         }
 
         ASSERT(textRun->isNonWhitespace());
+        // Find out if this non-whitespace fragment has a hyphen where we can break.
+        if (m_style.shouldHyphenate && !m_hyphenationIsDisabled) {
+            if (!splitTextRun(*textRun)) {
+                ++m_textRunList;
+                break;
+            }
+        }
+
         // Non-breakable non-whitespace first run. Add it to the current line. -it overflows though.
         if (!m_currentLine.hasContent()) {
             handleOverflownRun();
@@ -495,6 +524,63 @@ void SimpleLineBreaker::collapseTrailingWhitespace()
     m_currentLine.collapseTrailingWhitespace();
 }
 
+std::optional<ContentPosition> SimpleLineBreaker::hyphenPositionBefore(const TextRun& textRun, ContentPosition before) const
+{
+    // Enough characters before the split position?
+    if (before <= textRun.start() + m_style.hyphenLimitBefore)
+        return { };
+
+    // Adjust before to accommodate the limit-after value (this is the last potential hyphen location).
+    before = std::min(before, textRun.end() - m_style.hyphenLimitAfter + 1);
+
+    auto hyphenLocation = m_contentProvider.hyphenPositionBefore(textRun.start(), textRun.end(), before);
+    if (!hyphenLocation)
+        return { };
+
+    ASSERT(hyphenLocation >= textRun.start() && hyphenLocation <= textRun.end());
+    // Check if there are enough characters before and after the hyphen.
+    if (*hyphenLocation < textRun.start() + m_style.hyphenLimitBefore || m_style.hyphenLimitAfter > (textRun.end() - *hyphenLocation))
+        return { };
+
+    return hyphenLocation;
+}
+
+std::optional<ContentPosition> SimpleLineBreaker::adjustSplitPositionWithHyphenation(const TextRun& textRun, ContentPosition splitPosition, float leftSideWidth) const
+{
+    ASSERT(textRun.isNonWhitespace());
+
+    // Use hyphen?
+    if (!m_style.shouldHyphenate || m_hyphenationIsDisabled)
+        return { };
+
+    // Check if there are enough characters in the run.
+    auto runLength = textRun.length();
+    if (m_style.hyphenLimitBefore >= runLength || m_style.hyphenLimitAfter >= runLength || m_style.hyphenLimitBefore + m_style.hyphenLimitAfter > runLength)
+        return { };
+
+    // FIXME: This is a workaround for webkit.org/b/169613. See maxPrefixWidth computation in tryHyphenating().
+    // It does not work properly with non-collapsed leading tabs when font is enlarged.
+    auto adjustedAvailableWidth = m_currentLine.availableWidth() - m_style.hyphenStringWidth;
+    if (m_currentLine.hasContent())
+        adjustedAvailableWidth += m_style.font.spaceWidth();
+
+    if (!enoughWidthForHyphenation(adjustedAvailableWidth, m_style.font.pixelSize()))
+        return { };
+
+    // Find the split position where hyphen surely fits (we might be able to fit the hyphen at the split position).
+    auto left = textRun.start();
+    auto right = splitPosition;
+    while (leftSideWidth + m_style.hyphenStringWidth > m_currentLine.availableWidth()) {
+        if (--right <= left)
+            return { }; // No space for hyphen.
+        // FIXME: for correctness (kerning) we should instead measure the left side.
+        leftSideWidth -= m_contentProvider.width(right, right + 1, 0);
+    }
+
+    // Find out if there's an actual hyphen opportinity at this position (or before).
+    return hyphenPositionBefore(textRun, right + 1);
+}
+
 bool SimpleLineBreaker::splitTextRun(const TextRun& textRun)
 {
     // Single character handling.
@@ -539,16 +625,24 @@ TextRunSplitPair SimpleLineBreaker::split(const TextRun& textRun, float leftSide
         }
     }
 
-    // Keep at least one character on empty lines.f
+    // Keep at least one character on empty lines.
+    bool splitHasHypen = false;
     left = textRun.start();
     if (left >= right && !m_currentLine.hasContent()) {
         right = left + 1;
-        leftSideWidth = m_contentProvider.width(textRun.start(), right, 0);
+        leftSideWidth = m_contentProvider.width(left, right, 0);
+    } else if (textRun.isNonWhitespace()) {
+        if (auto hyphenPosition = adjustSplitPositionWithHyphenation(textRun, right, leftSideWidth)) {
+            splitHasHypen = true;
+            right = *hyphenPosition;
+            leftSideWidth = m_contentProvider.width(left, right, 0) + m_style.hyphenStringWidth;
+        }
     }
 
     auto rightSideWidth = m_contentProvider.width(right, textRun.end(), 0);
     if (textRun.isNonWhitespace())
-        return { TextRun::createNonWhitespaceRun(left, right, leftSideWidth), TextRun::createNonWhitespaceRun(right, textRun.end(), rightSideWidth) };
+        return { splitHasHypen ? TextRun::createNonWhitespaceRunWithHyphen(left, right, leftSideWidth) : TextRun::createNonWhitespaceRun(left, right, leftSideWidth),
+            TextRun::createNonWhitespaceRun(right, textRun.end(), rightSideWidth) };
 
     // We never split collapsed whitespace.
     ASSERT(textRun.isWhitespace());
index 46435da..31ca2c9 100644 (file)
@@ -33,6 +33,7 @@
 
 namespace WebCore {
 
+class FontCascade;
 class RenderStyle;
 
 namespace Layout {
@@ -62,6 +63,7 @@ private:
     struct Style {
         explicit Style(const RenderStyle&);
 
+        const FontCascade& font;
         bool wrapLines { false };
         bool breakAnyWordOnOverflow { false };
         bool breakFirstWordOnOverflow { false };
@@ -69,6 +71,12 @@ private:
         bool preWrap { false };
         bool preserveNewline { false };
         TextAlignMode textAlign { TextAlignMode::Left };
+        bool shouldHyphenate;
+        float hyphenStringWidth;
+        ItemPosition hyphenLimitBefore;
+        ItemPosition hyphenLimitAfter;
+        AtomicString locale;
+        std::optional<unsigned> hyphenLimitLines;
     };
 
     class TextRunList {
@@ -113,13 +121,14 @@ private:
             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 };
@@ -146,6 +155,8 @@ private:
     void collapseTrailingWhitespace();
     bool splitTextRun(const TextRun&);
     TextRunSplitPair split(const TextRun&, float leftSideMaximumWidth) const;
+    std::optional<ContentPosition> hyphenPositionBefore(const TextRun&, ContentPosition before) const;
+    std::optional<ContentPosition> adjustSplitPositionWithHyphenation(const TextRun&, ContentPosition splitPosition, float leftSideWidth) const;
     LineConstraint lineConstraint(float verticalPosition);
     float verticalPosition() const { return m_numberOfLines * m_lineHeight; }
 
@@ -163,6 +174,8 @@ private:
     unsigned m_numberOfLines { 0 };
     bool m_previousLineHasNonForcedContent { false };
     float m_lineHeight { 0 };
+    bool m_hyphenationIsDisabled { false };
+    unsigned m_numberOfPrecedingLinesWithHyphen { 0 };
 };
 
 inline std::optional<TextRun> SimpleLineBreaker::TextRunList::current() const