Implement -apple-trailing-word: -apple-partially-balanced
authormmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Mar 2015 20:48:04 +0000 (20:48 +0000)
committermmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Mar 2015 20:48:04 +0000 (20:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=142253

Reviewed by David Hyatt.

Source/WebCore:

This patch implements a history mechanism for line breaking. In particular, this patch partitions
the updates to the current line breaking location into two kinds: conceptually new breaking locations,
and fixups to existing locations. Then, this patch remembers all the fixed up breaking locations, up
to a maximum number of remembered locations.

The patch then uses this memory to change the line-breaking selection based on the rules of
-apple-trailing-word.

Test: fast/text/trailing-word.html

* rendering/line/BreakingContextInlineHeaders.h:
Use InlineIteratorHistory as a proxy for the current breaking location. Note that all these functions
are inlined, so the overhead should be next to nothing when -apple-trailing-word is not in use.
(WebCore::BreakingContext::BreakingContext): Use InlineIteratorHistory instead of InlineIterator
(WebCore::BreakingContext::lineBreak): Ditto.
(WebCore::BreakingContext::clearLineBreakIfFitsOnLine): Ditto
(WebCore::BreakingContext::commitLineBreakAtCurrentWidth): Ditto
(WebCore::BreakingContext::InlineIteratorHistory::InlineIteratorHistory): Keeps track of historical
breaking locations
(WebCore::BreakingContext::InlineIteratorHistory::push): Remember a new breaking location
(WebCore::BreakingContext::InlineIteratorHistory::update): Update an existing breaking location
(WebCore::BreakingContext::InlineIteratorHistory::renderer): Forwarded to the current breaking location
(WebCore::BreakingContext::InlineIteratorHistory::offset): Ditto
(WebCore::BreakingContext::InlineIteratorHistory::atTextParagraphSeparator):  Ditto
(WebCore::BreakingContext::InlineIteratorHistory::previousInSameNode): Ditto
(WebCore::BreakingContext::InlineIteratorHistory::get): Get one of the remembered breaking locations
(WebCore::BreakingContext::InlineIteratorHistory::current): Get the current breaking location
(WebCore::BreakingContext::InlineIteratorHistory::historyLength):
(WebCore::BreakingContext::InlineIteratorHistory::moveTo): Forwarded to the current breaking location.
(WebCore::BreakingContext::InlineIteratorHistory::increment): Ditto
(WebCore::BreakingContext::InlineIteratorHistory::clear): Ditto
(WebCore::BreakingContext::handleBR): Use InlineIteratorHistory instead of InlineIterator
(WebCore::BreakingContext::handleFloat): Ditto
(WebCore::BreakingContext::handleText): Use InlineIteratorHistory instead of InlineIterator
(WebCore::BreakingContext::commitAndUpdateLineBreakIfNeeded): Style
(WebCore::checkMidpoints): Use InlineIteratorHistory instead of InlineIterator
(WebCore::BreakingContext::handleEndOfLine): If -apple-trailing-word is in effect, use
optimalLineBreakLocationForTrailingWord().
(WebCore::BreakingContext::optimalLineBreakLocationForTrailingWord): Use the remembered breaking
locations and choose the optimal one.
(WebCore::BreakingContext::lineBreakRef): Deleted.

LayoutTests:

* fast/text/trailing-word-expected.html: Added.
* fast/text/trailing-word.html: Added.

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

LayoutTests/ChangeLog
LayoutTests/fast/text/trailing-word-expected.html [new file with mode: 0644]
LayoutTests/fast/text/trailing-word.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/rendering/line/BreakingContextInlineHeaders.h

index f8826032595a72a52f2a6cde745bf50549fcc2db..611b885bdd9f8adec31191c535ed947ac0d741e7 100644 (file)
@@ -1,3 +1,13 @@
+2015-03-04  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        Implement -apple-trailing-word: -apple-partially-balanced
+        https://bugs.webkit.org/show_bug.cgi?id=142253
+
+        Reviewed by David Hyatt.
+
+        * fast/text/trailing-word-expected.html: Added.
+        * fast/text/trailing-word.html: Added.
+
 2015-03-04  Marcos Chavarría Teijeiro  <chavarria1991@gmail.com>
 
         Unreviewed Gardening 4th March
diff --git a/LayoutTests/fast/text/trailing-word-expected.html b/LayoutTests/fast/text/trailing-word-expected.html
new file mode 100644 (file)
index 0000000..f6024ad
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+This test makes sure that -apple-trailing-word: -apple-partially-balanced breaks lines correctly.
+<div style="font-family: Ahem;">
+<div>ABC DEF GHI JKL MNO PQRS TUVW XYZ ABC DEF GHI JKL<br>MNO</div>
+<div>ABC DEF GHI JKL MNO PQRS TUVW XYZ ABC DEF GHI<br>JKL MNO</div>
+<div>ABC DEF GHI JKL MNO PQRS TUVW XYZ ABC DEF G H<br>I J K</div>
+<div>ABC DEF GHI JKL MNO PQRS TUVW XYZ ABC DEF GHI JKL<br>MNO PQRS</div>
+<div>ABC DEF GHI JKL MNO PQRS TUVW XYZ ABC DEF GHI JKL<br>MNO PQRS TUV WXYZ ABC DEF GHI JKL MNO PQRS TUV<br>WXYZ</div>
+</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/trailing-word.html b/LayoutTests/fast/text/trailing-word.html
new file mode 100644 (file)
index 0000000..e0824e5
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+This test makes sure that -apple-trailing-word: -apple-partially-balanced breaks lines correctly.
+<div style="font-family: Ahem;">
+<div>ABC DEF GHI JKL MNO PQRS TUVW XYZ ABC DEF GHI JKL MNO</div>
+<div style="-apple-trailing-word: -apple-partially-balanced;">ABC DEF GHI JKL MNO PQRS TUVW XYZ ABC DEF GHI JKL MNO</div>
+<div style="-apple-trailing-word: -apple-partially-balanced;">ABC DEF GHI JKL MNO PQRS TUVW XYZ ABC DEF G H I J K</div>
+<div style="-apple-trailing-word: -apple-partially-balanced;">ABC DEF GHI JKL MNO PQRS TUVW XYZ ABC DEF GHI JKL MNO PQRS</div>
+<div style="-apple-trailing-word: -apple-partially-balanced;">ABC DEF GHI JKL MNO PQRS TUVW XYZ ABC DEF GHI JKL MNO PQRS TUV WXYZ ABC DEF GHI JKL MNO PQRS TUV WXYZ</div>
+</div>
+</body>
+</html>
index 2d573921de10e2409fa6650e52a165ad14fded8f..f1d8db9d56c5d8dab397f04eb30e77ac7bbdd82a 100644 (file)
@@ -1,3 +1,52 @@
+2015-03-04  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        Implement -apple-trailing-word: -apple-partially-balanced
+        https://bugs.webkit.org/show_bug.cgi?id=142253
+
+        Reviewed by David Hyatt.
+
+        This patch implements a history mechanism for line breaking. In particular, this patch partitions
+        the updates to the current line breaking location into two kinds: conceptually new breaking locations,
+        and fixups to existing locations. Then, this patch remembers all the fixed up breaking locations, up
+        to a maximum number of remembered locations.
+
+        The patch then uses this memory to change the line-breaking selection based on the rules of
+        -apple-trailing-word.
+
+        Test: fast/text/trailing-word.html
+
+        * rendering/line/BreakingContextInlineHeaders.h:
+        Use InlineIteratorHistory as a proxy for the current breaking location. Note that all these functions
+        are inlined, so the overhead should be next to nothing when -apple-trailing-word is not in use.
+        (WebCore::BreakingContext::BreakingContext): Use InlineIteratorHistory instead of InlineIterator
+        (WebCore::BreakingContext::lineBreak): Ditto.
+        (WebCore::BreakingContext::clearLineBreakIfFitsOnLine): Ditto
+        (WebCore::BreakingContext::commitLineBreakAtCurrentWidth): Ditto
+        (WebCore::BreakingContext::InlineIteratorHistory::InlineIteratorHistory): Keeps track of historical
+        breaking locations
+        (WebCore::BreakingContext::InlineIteratorHistory::push): Remember a new breaking location
+        (WebCore::BreakingContext::InlineIteratorHistory::update): Update an existing breaking location
+        (WebCore::BreakingContext::InlineIteratorHistory::renderer): Forwarded to the current breaking location
+        (WebCore::BreakingContext::InlineIteratorHistory::offset): Ditto
+        (WebCore::BreakingContext::InlineIteratorHistory::atTextParagraphSeparator):  Ditto
+        (WebCore::BreakingContext::InlineIteratorHistory::previousInSameNode): Ditto
+        (WebCore::BreakingContext::InlineIteratorHistory::get): Get one of the remembered breaking locations
+        (WebCore::BreakingContext::InlineIteratorHistory::current): Get the current breaking location
+        (WebCore::BreakingContext::InlineIteratorHistory::historyLength):
+        (WebCore::BreakingContext::InlineIteratorHistory::moveTo): Forwarded to the current breaking location.
+        (WebCore::BreakingContext::InlineIteratorHistory::increment): Ditto
+        (WebCore::BreakingContext::InlineIteratorHistory::clear): Ditto
+        (WebCore::BreakingContext::handleBR): Use InlineIteratorHistory instead of InlineIterator
+        (WebCore::BreakingContext::handleFloat): Ditto
+        (WebCore::BreakingContext::handleText): Use InlineIteratorHistory instead of InlineIterator
+        (WebCore::BreakingContext::commitAndUpdateLineBreakIfNeeded): Style
+        (WebCore::checkMidpoints): Use InlineIteratorHistory instead of InlineIterator
+        (WebCore::BreakingContext::handleEndOfLine): If -apple-trailing-word is in effect, use
+        optimalLineBreakLocationForTrailingWord().
+        (WebCore::BreakingContext::optimalLineBreakLocationForTrailingWord): Use the remembered breaking
+        locations and choose the optimal one.
+        (WebCore::BreakingContext::lineBreakRef): Deleted.
+
 2015-03-04  Timothy Horton  <timothy_horton@apple.com>
 
         <attachment> title text disappears when dragging
index 7d90ecef7035f2d97e15d4640cd208157e14fd10..26d408b0af7dfd0eba70b4812bed0c61eab6637b 100644 (file)
@@ -68,7 +68,11 @@ public:
         : m_lineBreaker(lineBreaker)
         , m_resolver(resolver)
         , m_current(resolver.position())
-        , m_lineBreak(resolver.position())
+#if ENABLE(CSS_TRAILING_WORD)
+        , m_lineBreakHistory(InlineIterator(resolver.position()), block.style().trailingWord() == TrailingWord::PartiallyBalanced ? 5 : 1)
+#else
+        , m_lineBreakHistory(InlineIterator(resolver.position()), 1)
+#endif
         , m_block(block)
         , m_lastObject(m_current.renderer())
         , m_nextObject(nullptr)
@@ -101,8 +105,7 @@ public:
     }
 
     RenderObject* currentObject() { return m_current.renderer(); }
-    InlineIterator lineBreak() { return m_lineBreak; }
-    InlineIterator& lineBreakRef() {return m_lineBreak; }
+    InlineIterator lineBreak() { return m_lineBreakHistory.current(); }
     LineWidth& lineWidth() { return m_width; }
     bool atEnd() { return m_atEnd; }
 
@@ -119,25 +122,88 @@ public:
     bool canBreakAtThisPosition();
     void commitAndUpdateLineBreakIfNeeded();
     InlineIterator handleEndOfLine();
+#if ENABLE(CSS_TRAILING_WORD)
+    InlineIterator optimalLineBreakLocationForTrailingWord();
+#endif
 
     void clearLineBreakIfFitsOnLine(bool ignoringTrailingSpace = false)
     {
         if (m_width.fitsOnLine(ignoringTrailingSpace) || m_lastWS == NOWRAP)
-            m_lineBreak.clear();
+            m_lineBreakHistory.clear();
     }
 
     void commitLineBreakAtCurrentWidth(RenderObject& object, unsigned offset = 0, int nextBreak = -1)
     {
         m_width.commit();
-        m_lineBreak.moveTo(&object, offset, nextBreak);
+        m_lineBreakHistory.moveTo(&object, offset, nextBreak);
     }
 
 private:
+    // This class keeps a sliding window of the past n locations for an InlineIterator.
+    class InlineIteratorHistory : private Vector<InlineIterator, 1> {
+    public:
+        InlineIteratorHistory() = delete;
+        InlineIteratorHistory(const InlineIterator& initial, size_t capacity)
+            : m_capacity(capacity)
+        {
+            ASSERT(capacity > 0);
+            this->append(initial);
+        }
+
+        void push(std::function<void(InlineIterator& modifyMe)> updater)
+        {
+            ASSERT(!this->isEmpty());
+            if (m_capacity != 1)
+                this->insert(0, InlineIterator(this->at(0)));
+            updater(this->at(0));
+            if (m_capacity != 1)
+                this->resize(m_capacity);
+        }
+
+        void update(std::function<void(InlineIterator& modifyMe)> updater)
+        {
+            ASSERT(!this->isEmpty());
+            updater(this->at(0));
+        }
+
+        RenderObject* renderer() const { return this->at(0).renderer(); }
+        unsigned offset() const { return this->at(0).offset(); }
+        bool atTextParagraphSeparator() const { return this->at(0).atTextParagraphSeparator(); }
+        UChar previousInSameNode() const { return this->at(0).previousInSameNode(); }
+        const InlineIterator& get(size_t i) const { return this->at(i); };
+        const InlineIterator& current() const { return get(0); }
+        size_t historyLength() const { return this->size(); }
+
+        void moveTo(RenderObject* object, unsigned offset, int nextBreak = -1)
+        {
+            push([&](InlineIterator& modifyMe) {
+                modifyMe.moveTo(object, offset, nextBreak);
+            });
+        }
+
+        void increment()
+        {
+            update([](InlineIterator& modifyMe) {
+                modifyMe.increment();
+            });
+        }
+
+        void clear()
+        {
+            push([](InlineIterator& modifyMe) {
+                modifyMe.clear();
+            });
+        }
+
+    private:
+        const size_t m_capacity;
+    };
+
     LineBreaker& m_lineBreaker;
     InlineBidiResolver& m_resolver;
 
     InlineIterator m_current;
-    InlineIterator m_lineBreak;
+    InlineIteratorHistory m_lineBreakHistory;
     InlineIterator m_startOfIgnoredSpaces;
 
     RenderBlockFlow& m_block;
@@ -228,8 +294,10 @@ inline void BreakingContext::handleBR(EClear& clear)
 {
     if (m_width.fitsOnLine()) {
         RenderObject* br = m_current.renderer();
-        m_lineBreak.moveToStartOf(br);
-        m_lineBreak.increment();
+        m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
+            modifyMe.moveToStartOf(br);
+            modifyMe.increment();
+        });
 
         // A <br> always breaks a line, so don't let the line be collapsed
         // away. Also, the space at the end of a line with a <br> does not
@@ -350,9 +418,9 @@ inline void BreakingContext::handleFloat()
     // it after moving to next line (in clearFloats() func)
     if (m_floatsFitOnLine && m_width.fitsOnLineExcludingTrailingWhitespace(m_block.logicalWidthForFloat(floatingObject))) {
         m_lineBreaker.positionNewFloatOnLine(floatingObject, m_lastFloatFromPreviousLine, m_lineInfo, m_width);
-        if (m_lineBreak.renderer() == m_current.renderer()) {
-            ASSERT(!m_lineBreak.offset());
-            m_lineBreak.increment();
+        if (m_lineBreakHistory.renderer() == m_current.renderer()) {
+            ASSERT(!m_lineBreakHistory.offset());
+            m_lineBreakHistory.increment();
         }
     } else
         m_floatsFitOnLine = false;
@@ -610,9 +678,9 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
         auto& combineRenderer = downcast<RenderCombineText>(*m_current.renderer());
         combineRenderer.combineText();
         // The length of the renderer's text may have changed. Increment stale iterator positions
-        if (iteratorIsBeyondEndOfRenderCombineText(m_lineBreak, combineRenderer)) {
+        if (iteratorIsBeyondEndOfRenderCombineText(m_lineBreakHistory.current(), combineRenderer)) {
             ASSERT(iteratorIsBeyondEndOfRenderCombineText(m_resolver.position(), combineRenderer));
-            m_lineBreak.increment();
+            m_lineBreakHistory.increment();
             m_resolver.increment();
         }
     }
@@ -760,30 +828,34 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
                     // additional whitespace.
                     if (!m_width.fitsOnLineIncludingExtraWidth(charWidth)) {
                         lineWasTooWide = true;
-                        m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
-                        m_lineBreaker.skipTrailingWhitespace(m_lineBreak, m_lineInfo);
+                        m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
+                            modifyMe.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
+                            m_lineBreaker.skipTrailingWhitespace(modifyMe, m_lineInfo);
+                        });
                     }
                 }
                 if (lineWasTooWide || !m_width.fitsOnLine()) {
                     if (canHyphenate && !m_width.fitsOnLine()) {
-                        tryHyphenating(&renderText, font, style.locale(), consecutiveHyphenatedLines, m_blockStyle.hyphenationLimitLines(), style.hyphenationLimitBefore(), style.hyphenationLimitAfter(), lastSpace, m_current.offset(), m_width.currentWidth() - additionalTempWidth, m_width.availableWidth(), isFixedPitch, m_collapseWhiteSpace, lastSpaceWordSpacing, m_lineBreak, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated);
+                        m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
+                            tryHyphenating(&renderText, font, style.locale(), consecutiveHyphenatedLines, m_blockStyle.hyphenationLimitLines(), style.hyphenationLimitBefore(), style.hyphenationLimitAfter(), lastSpace, m_current.offset(), m_width.currentWidth() - additionalTempWidth, m_width.availableWidth(), isFixedPitch, m_collapseWhiteSpace, lastSpaceWordSpacing, modifyMe, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated);
+                        });
                         if (m_lineBreaker.m_hyphenated) {
                             m_atEnd = true;
                             return false;
                         }
                     }
-                    if (m_lineBreak.atTextParagraphSeparator()) {
+                    if (m_lineBreakHistory.atTextParagraphSeparator()) {
                         if (!stoppedIgnoringSpaces && m_current.offset() > 0)
                             ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
-                        m_lineBreak.increment();
+                        m_lineBreakHistory.increment();
                         m_lineInfo.setPreviousLineBrokeCleanly(true);
-                        wordMeasurement.endOffset = m_lineBreak.offset();
+                        wordMeasurement.endOffset = m_lineBreakHistory.offset();
                     }
-                    if (m_lineBreak.offset() && downcast<RenderText>(m_lineBreak.renderer()) && downcast<RenderText>(*m_lineBreak.renderer()).textLength() && downcast<RenderText>(*m_lineBreak.renderer()).characterAt(m_lineBreak.offset() - 1) == softHyphen && style.hyphens() != HyphensNone)
+                    if (m_lineBreakHistory.offset() && downcast<RenderText>(m_lineBreakHistory.renderer()) && downcast<RenderText>(*m_lineBreakHistory.renderer()).textLength() && downcast<RenderText>(*m_lineBreakHistory.renderer()).characterAt(m_lineBreakHistory.offset() - 1) == softHyphen && style.hyphens() != HyphensNone)
                         hyphenated = true;
-                    if (m_lineBreak.offset() && m_lineBreak.offset() != (unsigned)wordMeasurement.endOffset && !wordMeasurement.width) {
+                    if (m_lineBreakHistory.offset() && m_lineBreakHistory.offset() != (unsigned)wordMeasurement.endOffset && !wordMeasurement.width) {
                         if (charWidth) {
-                            wordMeasurement.endOffset = m_lineBreak.offset();
+                            wordMeasurement.endOffset = m_lineBreakHistory.offset();
                             wordMeasurement.width = charWidth;
                         }
                     }
@@ -807,7 +879,7 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
                 if (!stoppedIgnoringSpaces && m_current.offset())
                     ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
                 commitLineBreakAtCurrentWidth(*m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
-                m_lineBreak.increment();
+                m_lineBreakHistory.increment();
                 m_lineInfo.setPreviousLineBrokeCleanly(true);
                 return true;
             }
@@ -823,7 +895,7 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
             if (midWordBreak && !U16_IS_TRAIL(c) && !(U_GET_GC_MASK(c) & U_GC_M_MASK)) {
                 // Remember this as a breakable position in case
                 // adding the end width forces a break.
-                m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
+                m_lineBreakHistory.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
                 midWordBreak &= (breakWords || breakAll);
             }
 
@@ -879,7 +951,7 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
 
         if (!m_currentCharacterIsWS && previousCharacterIsWS) {
             if (m_autoWrap && m_currentStyle->breakOnlyAfterWhiteSpace())
-                m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
+                m_lineBreakHistory.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
         }
 
         if (m_collapseWhiteSpace && m_currentCharacterIsSpace && !m_ignoringSpaces)
@@ -917,10 +989,13 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
     m_includeEndWidth = false;
 
     if (!m_width.fitsOnLine()) {
-        if (canHyphenate)
-            tryHyphenating(&renderText, font, style.locale(), consecutiveHyphenatedLines, m_blockStyle.hyphenationLimitLines(), style.hyphenationLimitBefore(), style.hyphenationLimitAfter(), lastSpace, m_current.offset(), m_width.currentWidth() - additionalTempWidth, m_width.availableWidth(), isFixedPitch, m_collapseWhiteSpace, lastSpaceWordSpacing, m_lineBreak, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated);
+        if (canHyphenate) {
+            m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
+                tryHyphenating(&renderText, font, style.locale(), consecutiveHyphenatedLines, m_blockStyle.hyphenationLimitLines(), style.hyphenationLimitBefore(), style.hyphenationLimitAfter(), lastSpace, m_current.offset(), m_width.currentWidth() - additionalTempWidth, m_width.availableWidth(), isFixedPitch, m_collapseWhiteSpace, lastSpaceWordSpacing, modifyMe, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated);
+            });
+        }
 
-        if (!hyphenated && m_lineBreak.previousInSameNode() == softHyphen && style.hyphens() != HyphensNone) {
+        if (!hyphenated && m_lineBreakHistory.previousInSameNode() == softHyphen && style.hyphens() != HyphensNone) {
             hyphenated = true;
             m_atEnd = true;
         }
@@ -1008,13 +1083,12 @@ inline void BreakingContext::commitAndUpdateLineBreakIfNeeded()
 
     if (!m_current.renderer()->isFloatingOrOutOfFlowPositioned()) {
         m_lastObject = m_current.renderer();
-        if (m_lastObject->isReplaced() && m_autoWrap && !m_lastObject->isRubyRun() && (!m_lastObject->isImage() || m_allowImagesToBreak) && (!is<RenderListMarker>(*m_lastObject) || downcast<RenderListMarker>(*m_lastObject).isInside())) {
+        if (m_lastObject->isReplaced() && m_autoWrap && !m_lastObject->isRubyRun() && (!m_lastObject->isImage() || m_allowImagesToBreak) && (!is<RenderListMarker>(*m_lastObject) || downcast<RenderListMarker>(*m_lastObject).isInside()))
             commitLineBreakAtCurrentWidth(*m_nextObject);
-        }
     }
 }
 
-inline TrailingObjects::CollapseFirstSpaceOrNot checkMidpoints(LineMidpointState& lineMidpointState, InlineIterator& lBreak)
+inline TrailingObjects::CollapseFirstSpaceOrNot checkMidpoints(LineMidpointState& lineMidpointState, const InlineIterator& lBreak)
 {
     // Check to see if our last midpoint is a start point beyond the line break. If so,
     // shave it off the list, and shave off a trailing space if the previous end point doesn't
@@ -1040,12 +1114,12 @@ inline TrailingObjects::CollapseFirstSpaceOrNot checkMidpoints(LineMidpointState
 
 inline InlineIterator BreakingContext::handleEndOfLine()
 {
-    if (m_lineBreak == m_resolver.position()) {
-        if (!m_lineBreak.renderer() || !m_lineBreak.renderer()->isBR()) {
+    if (m_lineBreakHistory.current() == m_resolver.position()) {
+        if (!m_lineBreakHistory.renderer() || !m_lineBreakHistory.renderer()->isBR()) {
             // we just add as much as possible
             if (m_blockStyle.whiteSpace() == PRE && !m_current.offset())
                 commitLineBreakAtCurrentWidth(*m_lastObject, m_lastObject->isText() ? m_lastObject->length() : 0);
-            else if (m_lineBreak.renderer()) {
+            else if (m_lineBreakHistory.renderer()) {
                 // Don't ever break in the middle of a word if we can help it.
                 // There's no room at all. We just have to be on this line,
                 // even though we'll spill out.
@@ -1053,33 +1127,76 @@ inline InlineIterator BreakingContext::handleEndOfLine()
             }
         }
         // make sure we consume at least one char/object.
-        if (m_lineBreak == m_resolver.position())
-            m_lineBreak.increment();
+        if (m_lineBreakHistory.current() == m_resolver.position())
+            m_lineBreakHistory.increment();
     } else if (!m_current.offset() && !m_width.committedWidth() && m_width.uncommittedWidth() && !m_hadUncommittedWidthBeforeCurrent) {
         // Do not push the current object to the next line, when this line has some content, but it is still considered empty.
         // Empty inline elements like <span></span> can produce such lines and now we just ignore these break opportunities
         // at the start of a line, if no width has been committed yet.
         // Behave as if it was actually empty and consume at least one object.
-        m_lineBreak.increment();
+        m_lineBreakHistory.increment();
     }
 
     // Sanity check our midpoints.
-    TrailingObjects::CollapseFirstSpaceOrNot collapsed = checkMidpoints(m_lineMidpointState, m_lineBreak);
+    TrailingObjects::CollapseFirstSpaceOrNot collapsed = checkMidpoints(m_lineMidpointState, m_lineBreakHistory.current());
 
-    m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, m_lineBreak, collapsed);
+    m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, m_lineBreakHistory.current(), collapsed);
 
     // We might have made lineBreak an iterator that points past the end
     // of the object. Do this adjustment to make it point to the start
     // of the next object instead to avoid confusing the rest of the
     // code.
-    if (m_lineBreak.offset()) {
-        m_lineBreak.setOffset(m_lineBreak.offset() - 1);
-        m_lineBreak.increment();
+    if (m_lineBreakHistory.offset()) {
+        m_lineBreakHistory.update([](InlineIterator& modifyMe) {
+            modifyMe.setOffset(modifyMe.offset() - 1);
+            modifyMe.increment();
+        });
     }
 
-    return m_lineBreak;
+#if ENABLE(CSS_TRAILING_WORD)
+    if (m_blockStyle.trailingWord() == TrailingWord::PartiallyBalanced)
+        return optimalLineBreakLocationForTrailingWord();
+#endif
+    return m_lineBreakHistory.current();
 }
 
+#if ENABLE(CSS_TRAILING_WORD)
+inline InlineIterator BreakingContext::optimalLineBreakLocationForTrailingWord()
+{
+    const unsigned longTrailingWordLength = 20;
+    const float optimalTrailingLineRatio = 0.1;
+    InlineIterator lineBreak = m_lineBreakHistory.current();
+    if (!lineBreak.renderer() || !m_lineInfo.isFirstLine() || bidiNextSkippingEmptyInlines(*lineBreak.root(), lineBreak.renderer()) || !is<RenderText>(lineBreak.renderer()))
+        return lineBreak;
+    RenderText& renderText = downcast<RenderText>(*lineBreak.renderer());
+    // Don't even bother measuring if our remaining line has many characters
+    if (renderText.textLength() == lineBreak.offset() || renderText.textLength() - lineBreak.offset() > longTrailingWordLength)
+        return lineBreak;
+    bool isLooseCJKMode = m_renderTextInfo.m_text != &renderText && m_renderTextInfo.m_lineBreakIterator.isLooseCJKMode();
+    bool breakNBSP = m_autoWrap && m_currentStyle->nbspMode() == SPACE;
+    int nextBreakablePosition = lineBreak.nextBreakablePosition();
+    isBreakable(m_renderTextInfo.m_lineBreakIterator, lineBreak.offset() + 1, nextBreakablePosition, breakNBSP, isLooseCJKMode);
+    if (nextBreakablePosition < 0 || static_cast<unsigned>(nextBreakablePosition) != renderText.textLength())
+        return lineBreak;
+    const RenderStyle& style = lineStyle(renderText, m_lineInfo);
+    const FontCascade& font = style.fontCascade();
+    HashSet<const Font*> dummyFonts;
+    InlineIterator best = lineBreak;
+    for (size_t i = 1; i < m_lineBreakHistory.historyLength(); ++i) {
+        const InlineIterator& candidate = m_lineBreakHistory.get(i);
+        if (candidate.renderer() != lineBreak.renderer())
+            return best;
+        float width = textWidth(&renderText, candidate.offset(), renderText.textLength() - candidate.offset(), font, 0, font.isFixedPitch(), m_collapseWhiteSpace, dummyFonts);
+        if (width > m_width.availableWidth())
+            return best;
+        if (width / m_width.availableWidth() > optimalTrailingLineRatio) // Subsequent line is long enough
+            return candidate;
+        best = candidate;
+    }
+    return best;
+}
+#endif
+
 }
 
 #endif // BreakingContextInlineHeaders_h