Allow PAL to log messages
[WebKit-https.git] / Source / WebCore / rendering / SimpleLineLayout.cpp
index aba48b6..1eb4677 100644 (file)
 #include "HitTestLocation.h"
 #include "HitTestRequest.h"
 #include "HitTestResult.h"
+#include "Hyphenation.h"
 #include "InlineTextBox.h"
 #include "LineWidth.h"
+#include "Logging.h"
 #include "PaintInfo.h"
 #include "RenderBlockFlow.h"
 #include "RenderChildIterator.h"
+#include "RenderFragmentedFlow.h"
+#include "RenderLineBreak.h"
+#include "RenderMultiColumnFlow.h"
 #include "RenderStyle.h"
 #include "RenderText.h"
 #include "RenderTextControl.h"
 #include "RenderView.h"
 #include "Settings.h"
 #include "SimpleLineLayoutFlowContents.h"
-#include "SimpleLineLayoutFlowContentsIterator.h"
 #include "SimpleLineLayoutFunctions.h"
+#include "SimpleLineLayoutTextFragmentIterator.h"
 #include "Text.h"
 #include "TextPaintStyle.h"
+#include <pal/Logging.h>
 
 namespace WebCore {
 namespace SimpleLineLayout {
 
+#ifndef NDEBUG
+#define SET_REASON_AND_RETURN_IF_NEEDED(reason, reasons, includeReasons) { \
+        reasons |= reason; \
+        if (includeReasons == IncludeReasons::First) \
+            return reasons; \
+    }
+#else
+#define SET_REASON_AND_RETURN_IF_NEEDED(reason, reasons, includeReasons) { \
+        ASSERT_UNUSED(includeReasons, includeReasons == IncludeReasons::First); \
+        reasons |= reason; \
+        return reasons; \
+    }
+#endif
+
+
+template <typename CharacterType> AvoidanceReasonFlags canUseForCharacter(CharacterType, bool textIsJustified, IncludeReasons);
+
+template<> AvoidanceReasonFlags canUseForCharacter(UChar character, bool textIsJustified, IncludeReasons includeReasons)
+{
+    AvoidanceReasonFlags reasons = { };
+    if (textIsJustified) {
+        // Include characters up to Latin Extended-B and some punctuation range when text is justified.
+        bool isLatinIncludingExtendedB = character <= 0x01FF;
+        bool isPunctuationRange = character >= 0x2010 && character <= 0x2027;
+        if (!(isLatinIncludingExtendedB || isPunctuationRange))
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowHasJustifiedNonLatinText, reasons, includeReasons);
+    }
+
+    if (U16_IS_SURROGATE(character))
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowTextHasSurrogatePair, reasons, includeReasons);
+    
+    UCharDirection direction = u_charDirection(character);
+    if (direction == U_RIGHT_TO_LEFT || direction == U_RIGHT_TO_LEFT_ARABIC
+        || direction == U_RIGHT_TO_LEFT_EMBEDDING || direction == U_RIGHT_TO_LEFT_OVERRIDE
+        || direction == U_LEFT_TO_RIGHT_EMBEDDING || direction == U_LEFT_TO_RIGHT_OVERRIDE
+        || direction == U_POP_DIRECTIONAL_FORMAT || direction == U_BOUNDARY_NEUTRAL)
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowTextHasDirectionCharacter, reasons, includeReasons);
+
+    return reasons;
+}
+
+template<> AvoidanceReasonFlags canUseForCharacter(LChar, bool, IncludeReasons)
+{
+    return { };
+}
+
 template <typename CharacterType>
-static bool canUseForText(const CharacterType* text, unsigned length, const Font& font)
+static AvoidanceReasonFlags canUseForText(const CharacterType* text, unsigned length, const FontCascade& fontCascade, std::optional<float> lineHeightConstraint,
+    bool textIsJustified, IncludeReasons includeReasons)
 {
-    // FIXME: <textarea maxlength=0> generates empty text node.
-    if (!length)
-        return false;
+    AvoidanceReasonFlags reasons = { };
+    auto& primaryFont = fontCascade.primaryFont();
+    auto& fontMetrics = primaryFont.fontMetrics();
+    auto availableSpaceForGlyphAscent = fontMetrics.ascent();
+    auto availableSpaceForGlyphDescent = fontMetrics.descent();
+    if (lineHeightConstraint) {
+        auto lineHeightPadding = *lineHeightConstraint - fontMetrics.height();
+        availableSpaceForGlyphAscent += lineHeightPadding / 2;
+        availableSpaceForGlyphDescent += lineHeightPadding / 2;
+    }
+
     for (unsigned i = 0; i < length; ++i) {
-        UChar character = text[i];
-        if (character == ' ')
+        auto character = text[i];
+        if (FontCascade::treatAsSpace(character))
             continue;
 
-        // These would be easy to support.
-        if (character == noBreakSpace)
-            return false;
         if (character == softHyphen)
-            return false;
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowTextHasSoftHyphen, reasons, includeReasons);
 
-        UCharDirection direction = u_charDirection(character);
-        if (direction == U_RIGHT_TO_LEFT || direction == U_RIGHT_TO_LEFT_ARABIC
-            || direction == U_RIGHT_TO_LEFT_EMBEDDING || direction == U_RIGHT_TO_LEFT_OVERRIDE
-            || direction == U_LEFT_TO_RIGHT_EMBEDDING || direction == U_LEFT_TO_RIGHT_OVERRIDE
-            || direction == U_POP_DIRECTIONAL_FORMAT || direction == U_BOUNDARY_NEUTRAL)
-            return false;
+        auto characterReasons = canUseForCharacter(character, textIsJustified, includeReasons);
+        if (characterReasons != NoReason)
+            SET_REASON_AND_RETURN_IF_NEEDED(characterReasons, reasons, includeReasons);
 
-        if (!font.glyphForCharacter(character))
-            return false;
+        auto glyphData = fontCascade.glyphDataForCharacter(character, false);
+        if (!glyphData.isValid() || glyphData.font != &primaryFont)
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowPrimaryFontIsInsufficient, reasons, includeReasons);
+
+        if (lineHeightConstraint) {
+            auto bounds = primaryFont.boundsForGlyph(glyphData.glyph);
+            if (ceilf(-bounds.y()) > availableSpaceForGlyphAscent || ceilf(bounds.maxY()) > availableSpaceForGlyphDescent)
+                SET_REASON_AND_RETURN_IF_NEEDED(FlowFontHasOverflowGlyph, reasons, includeReasons);
+        }
     }
-    return true;
+    return reasons;
 }
 
-static bool canUseForText(const RenderText& textRenderer, const Font& font)
+static AvoidanceReasonFlags canUseForText(StringView text, const FontCascade& fontCascade, std::optional<float> lineHeightConstraint, bool textIsJustified, IncludeReasons includeReasons)
 {
-    if (textRenderer.is8Bit())
-        return canUseForText(textRenderer.characters8(), textRenderer.textLength(), font);
-    return canUseForText(textRenderer.characters16(), textRenderer.textLength(), font);
+    if (text.is8Bit())
+        return canUseForText(text.characters8(), text.length(), fontCascade, lineHeightConstraint, textIsJustified, includeReasons);
+    return canUseForText(text.characters16(), text.length(), fontCascade, lineHeightConstraint, textIsJustified, includeReasons);
 }
 
-bool canUseFor(const RenderBlockFlow& flow)
+static AvoidanceReasonFlags canUseForFontAndText(const RenderBlockFlow& flow, IncludeReasons includeReasons)
 {
-    if (!flow.frame().settings().simpleLineLayoutEnabled())
-        return false;
-    if (!flow.firstChild())
-        return false;
-    // This currently covers <blockflow>#text</blockflow> and mutiple (sibling) RenderText cases.
-    // The <blockflow><inline>#text</inline></blockflow> case is also popular and should be relatively easy to cover.
-    for (const auto& renderer : childrenOfType<RenderObject>(flow)) {
-        if (!is<RenderText>(renderer))
-            return false;
+    AvoidanceReasonFlags reasons = { };
+    // We assume that all lines have metrics based purely on the primary font.
+    const auto& style = flow.style();
+    auto& fontCascade = style.fontCascade();
+    if (fontCascade.primaryFont().isInterstitial())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowIsMissingPrimaryFont, reasons, includeReasons);
+    std::optional<float> lineHeightConstraint;
+    if (style.lineBoxContain() & LineBoxContainGlyphs)
+        lineHeightConstraint = lineHeightFromFlow(flow).toFloat();
+    bool flowIsJustified = style.textAlign() == JUSTIFY;
+    for (const auto& textRenderer : childrenOfType<RenderText>(flow)) {
+        // FIXME: Do not return until after checking all children.
+        if (!textRenderer.textLength())
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsEmpty, reasons, includeReasons);
+        if (textRenderer.isCombineText())
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsCombineText, reasons, includeReasons);
+        if (textRenderer.isCounter())
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsRenderCounter, reasons, includeReasons);
+        if (textRenderer.isQuote())
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsRenderQuote, reasons, includeReasons);
+        if (textRenderer.isTextFragment())
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsTextFragment, reasons, includeReasons);
+        if (textRenderer.isSVGInlineText())
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsSVGInlineText, reasons, includeReasons);
+        if (!textRenderer.canUseSimpleFontCodePath()) {
+            // No need to check the code path at this point. We already know it can't be simple.
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowHasComplexFontCodePath, reasons, includeReasons);
+        } else {
+            TextRun run(textRenderer.text());
+            run.setCharacterScanForCodePath(false);
+            if (style.fontCascade().codePath(run) != FontCascade::Simple)
+                SET_REASON_AND_RETURN_IF_NEEDED(FlowHasComplexFontCodePath, reasons, includeReasons);
+        }
+
+        auto textReasons = canUseForText(textRenderer.stringView(), fontCascade, lineHeightConstraint, flowIsJustified, includeReasons);
+        if (textReasons != NoReason)
+            SET_REASON_AND_RETURN_IF_NEEDED(textReasons, reasons, includeReasons);
     }
-    if (!flow.isHorizontalWritingMode())
-        return false;
-    if (flow.flowThreadState() != RenderObject::NotInsideFlowThread)
-        return false;
-    // Printing does pagination without a flow thread.
-    if (flow.document().paginated())
-        return false;
-    if (flow.hasOutline())
-        return false;
-    if (flow.isRubyText() || flow.isRubyBase())
-        return false;
-    if (flow.parent()->isDeprecatedFlexibleBox())
-        return false;
-    // FIXME: Implementation of wrap=hard looks into lineboxes.
-    if (flow.parent()->isTextArea() && flow.parent()->element()->fastHasAttribute(HTMLNames::wrapAttr))
-        return false;
-    // FIXME: Placeholders do something strange.
-    if (is<RenderTextControl>(*flow.parent()) && downcast<RenderTextControl>(*flow.parent()).textFormControlElement().placeholderElement())
-        return false;
-    const RenderStyle& style = flow.style();
-    if (style.textDecorationsInEffect() != TextDecorationNone)
-        return false;
-    if (style.textAlign() == JUSTIFY)
-        return false;
+    return reasons;
+}
+
+static AvoidanceReasonFlags canUseForStyle(const RenderStyle& style, IncludeReasons includeReasons)
+{
+    AvoidanceReasonFlags reasons = { };
+    if (style.textOverflow())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextOverflow, reasons, includeReasons);
+    if ((style.textDecorationsInEffect() & TextDecorationUnderline) && style.textUnderlinePosition() == TextUnderlinePositionUnder)
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedUnderlineDecoration, reasons, includeReasons);
     // Non-visible overflow should be pretty easy to support.
     if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE)
-        return false;
-    if (!style.textIndent().isZero())
-        return false;
-    if (!style.wordSpacing().isZero() || style.letterSpacing())
-        return false;
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasOverflowNotVisible, reasons, includeReasons);
     if (!style.isLeftToRightDirection())
-        return false;
-    if (style.lineBoxContain() != RenderStyle::initialLineBoxContain())
-        return false;
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowIsNotLTR, reasons, includeReasons);
+    if (!(style.lineBoxContain() & LineBoxContainBlock))
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineBoxContainProperty, reasons, includeReasons);
     if (style.writingMode() != TopToBottomWritingMode)
-        return false;
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowIsNotTopToBottom, reasons, includeReasons);
     if (style.lineBreak() != LineBreakAuto)
-        return false;
-    if (style.wordBreak() != NormalWordBreak)
-        return false;
-    if (style.unicodeBidi() != UBNormal || style.rtlOrdering() != LogicalOrder)
-        return false;
-    if (style.lineAlign() != LineAlignNone || style.lineSnap() != LineSnapNone)
-        return false;
-    if (style.hyphens() == HyphensAuto)
-        return false;
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineBreak, reasons, includeReasons);
+    if (style.unicodeBidi() != UBNormal)
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonNormalUnicodeBiDi, reasons, includeReasons);
+    if (style.rtlOrdering() != LogicalOrder)
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasRTLOrdering, reasons, includeReasons);
+    if (style.lineAlign() != LineAlignNone)
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineAlignEdges, reasons, includeReasons);
+    if (style.lineSnap() != LineSnapNone)
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineSnap, reasons, includeReasons);
     if (style.textEmphasisFill() != TextEmphasisFillFilled || style.textEmphasisMark() != TextEmphasisMarkNone)
-        return false;
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextEmphasisFillOrMark, reasons, includeReasons);
     if (style.textShadow())
-        return false;
-    if (style.textOverflow() || (flow.isAnonymousBlock() && flow.parent()->style().textOverflow()))
-        return false;
-    if (style.hasPseudoStyle(FIRST_LINE) || style.hasPseudoStyle(FIRST_LETTER))
-        return false;
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextShadow, reasons, includeReasons);
+    if (style.hasPseudoStyle(FIRST_LINE))
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLine, reasons, includeReasons);
+    if (style.hasPseudoStyle(FIRST_LETTER))
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLetter, reasons, includeReasons);
     if (style.hasTextCombine())
-        return false;
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextCombine, reasons, includeReasons);
     if (style.backgroundClip() == TextFillBox)
-        return false;
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextFillBox, reasons, includeReasons);
     if (style.borderFit() == BorderFitLines)
-        return false;
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasBorderFitLines, reasons, includeReasons);
     if (style.lineBreak() != LineBreakAuto)
-        return false;
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonAutoLineBreak, reasons, includeReasons);
+    if (style.nbspMode() != NBNORMAL)
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasWebKitNBSPMode, reasons, includeReasons);
+#if ENABLE(CSS_TRAILING_WORD)
+    if (style.trailingWord() != TrailingWord::Auto)
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonAutoTrailingWord, reasons, includeReasons);
+#endif
+    if (style.hyphens() == HyphensAuto) {
+        auto textReasons = canUseForText(style.hyphenString(), style.fontCascade(), std::nullopt, false, includeReasons);
+        if (textReasons != NoReason)
+            SET_REASON_AND_RETURN_IF_NEEDED(textReasons, reasons, includeReasons);
+    }
+    return reasons;
+}
 
+AvoidanceReasonFlags canUseForWithReason(const RenderBlockFlow& flow, IncludeReasons includeReasons)
+{
+#ifndef NDEBUG
+    static std::once_flag onceFlag;
+    std::call_once(onceFlag, [] {
+        PAL::registerNotifyCallback("com.apple.WebKit.showSimpleLineLayoutCoverage", WTF::Function<void()> { printSimpleLineLayoutCoverage });
+        PAL::registerNotifyCallback("com.apple.WebKit.showSimpleLineLayoutReasons", WTF::Function<void()> { printSimpleLineLayoutBlockList });
+        PAL::registerNotifyCallback("com.apple.WebKit.toggleSimpleLineLayout", WTF::Function<void()> { toggleSimpleLineLayout });
+    });
+#endif
+    AvoidanceReasonFlags reasons = { };
+    if (!flow.settings().simpleLineLayoutEnabled())
+        SET_REASON_AND_RETURN_IF_NEEDED(FeatureIsDisabled, reasons, includeReasons);
+    if (!flow.parent())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNoParent, reasons, includeReasons);
+    if (!flow.firstChild())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNoChild, reasons, includeReasons);
+    if (flow.fragmentedFlowState() != RenderObject::NotInsideFragmentedFlow) {
+        auto* fragmentedFlow = flow.enclosingFragmentedFlow();
+        if (!is<RenderMultiColumnFlow>(fragmentedFlow))
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowIsInsideANonMultiColumnThread, reasons, includeReasons);
+        auto& columnThread = downcast<RenderMultiColumnFlow>(*fragmentedFlow);
+        if (columnThread.parent() != &flow.view())
+            SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowIsNotTopLevel, reasons, includeReasons);
+        if (columnThread.hasColumnSpanner())
+            SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowHasColumnSpanner, reasons, includeReasons);
+        auto& style = flow.style();
+        if (style.verticalAlign() != BASELINE)
+            SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowVerticalAlign, reasons, includeReasons);
+        if (style.isFloating())
+            SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowIsFloating, reasons, includeReasons);
+    }
+    if (!flow.isHorizontalWritingMode())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasHorizonalWritingMode, reasons, includeReasons);
+    if (flow.hasOutline())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasOutline, reasons, includeReasons);
+    if (flow.isRubyText() || flow.isRubyBase())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowIsRuby, reasons, includeReasons);
+    if (flow.style().hangingPunctuation() != NoHangingPunctuation)
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasHangingPunctuation, reasons, includeReasons);
+    
+    // Printing does pagination without a flow thread.
+    if (flow.document().paginated())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowIsPaginated, reasons, includeReasons);
+    if (flow.firstLineBlock())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLine, reasons, includeReasons);
+    if (flow.isAnonymousBlock() && flow.parent()->style().textOverflow())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextOverflow, reasons, includeReasons);
+    if (flow.parent()->isDeprecatedFlexibleBox())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowIsDepricatedFlexBox, reasons, includeReasons);
+    // FIXME: Placeholders do something strange.
+    if (is<RenderTextControl>(*flow.parent()) && downcast<RenderTextControl>(*flow.parent()).textFormControlElement().placeholderElement())
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowParentIsPlaceholderElement, reasons, includeReasons);
+    // FIXME: Implementation of wrap=hard looks into lineboxes.
+    if (flow.parent()->isTextArea() && flow.parent()->element()->hasAttributeWithoutSynchronization(HTMLNames::wrapAttr))
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowParentIsTextAreaWithWrapping, reasons, includeReasons);
+    // This currently covers <blockflow>#text</blockflow>, <blockflow>#text<br></blockflow> and mutiple (sibling) RenderText cases.
+    // The <blockflow><inline>#text</inline></blockflow> case is also popular and should be relatively easy to cover.
+    for (const auto* child = flow.firstChild(); child;) {
+        if (child->selectionState() != RenderObject::SelectionNone)
+            SET_REASON_AND_RETURN_IF_NEEDED(FlowChildIsSelected, reasons, includeReasons);
+        if (is<RenderText>(*child)) {
+            child = child->nextSibling();
+            continue;
+        }
+        if (is<RenderLineBreak>(child) && !downcast<RenderLineBreak>(*child).isWBR() && child->style().clear() == CNONE) {
+            child = child->nextSibling();
+            continue;
+        }
+        SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonSupportedChild, reasons, includeReasons);
+        break;
+    }
+    auto styleReasons = canUseForStyle(flow.style(), includeReasons);
+    if (styleReasons != NoReason)
+        SET_REASON_AND_RETURN_IF_NEEDED(styleReasons, reasons, includeReasons);
     // We can't use the code path if any lines would need to be shifted below floats. This is because we don't keep per-line y coordinates.
     if (flow.containsFloats()) {
         float minimumWidthNeeded = std::numeric_limits<float>::max();
@@ -173,34 +333,25 @@ bool canUseFor(const RenderBlockFlow& flow)
 
             for (auto& floatingObject : *flow.floatingObjectSet()) {
                 ASSERT(floatingObject);
-#if ENABLE(CSS_SHAPES)
                 // if a float has a shape, we cannot tell if content will need to be shifted until after we lay it out,
                 // since the amount of space is not uniform for the height of the float.
                 if (floatingObject->renderer().shapeOutsideInfo())
-                    return false;
-#endif
-                float availableWidth = flow.availableLogicalWidthForLine(floatingObject->y(), false);
+                    SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedFloat, reasons, includeReasons);
+                float availableWidth = flow.availableLogicalWidthForLine(floatingObject->y(), DoNotIndentText);
                 if (availableWidth < minimumWidthNeeded)
-                    return false;
+                    SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedFloat, reasons, includeReasons);
             }
         }
     }
-    if (style.fontCascade().primaryFont().isSVGFont())
-        return false;
-    // We assume that all lines have metrics based purely on the primary font.
-    auto& primaryFont = style.fontCascade().primaryFont();
-    if (primaryFont.isLoading())
-        return false;
-    for (const auto& textRenderer : childrenOfType<RenderText>(flow)) {
-        if (textRenderer.isCombineText() || textRenderer.isCounter() || textRenderer.isQuote() || textRenderer.isTextFragment()
-            || textRenderer.isSVGInlineText())
-            return false;
-        if (style.fontCascade().codePath(TextRun(textRenderer.text())) != FontCascade::Simple)
-            return false;
-        if (!canUseForText(textRenderer, primaryFont))
-            return false;
-    }
-    return true;
+    auto fontAndTextReasons = canUseForFontAndText(flow, includeReasons);
+    if (fontAndTextReasons != NoReason)
+        SET_REASON_AND_RETURN_IF_NEEDED(fontAndTextReasons, reasons, includeReasons);
+    return reasons;
+}
+
+bool canUseFor(const RenderBlockFlow& flow)
+{
+    return canUseForWithReason(flow, IncludeReasons::First) == NoReason;
 }
 
 static float computeLineLeft(ETextAlign textAlign, float availableWidth, float committedWidth, float logicalLeftOffset)
@@ -227,180 +378,421 @@ static float computeLineLeft(ETextAlign textAlign, float availableWidth, float c
     return 0;
 }
 
+static void revertAllRunsOnCurrentLine(Layout::RunVector& runs)
+{
+    while (!runs.isEmpty() && !runs.last().isEndOfLine)
+        runs.removeLast();
+}
+
+static void revertRuns(Layout::RunVector& runs, unsigned positionToRevertTo, float width)
+{
+    while (runs.size()) {
+        auto& lastRun = runs.last();
+        if (lastRun.end <= positionToRevertTo)
+            break;
+        if (lastRun.start >= positionToRevertTo) {
+            // Revert this run completely.
+            width -= (lastRun.logicalRight - lastRun.logicalLeft);
+            runs.removeLast();
+        } else {
+            lastRun.logicalRight -= width;
+            width = 0;
+            lastRun.end = positionToRevertTo;
+            // Partial removal.
+            break;
+        }
+    }
+}
+
 class LineState {
 public:
     void setAvailableWidth(float width) { m_availableWidth = width; }
+    void setCollapedWhitespaceWidth(float width) { m_collapsedWhitespaceWidth = width; }
     void setLogicalLeftOffset(float offset) { m_logicalLeftOffset = offset; }
-    void setOverflowedFragment(const FlowContentsIterator::TextFragment& fragment) { m_overflowedFragment = fragment; }
+    void setOverflowedFragment(const TextFragmentIterator::TextFragment& fragment) { m_overflowedFragment = fragment; }
+    void setNeedsAllFragments()
+    {
+        ASSERT(!m_fragments);
+        m_fragments.emplace();
+    }
+    void setHyphenationDisabled() { m_hyphenationDisabled = true; }
+    bool isHyphenationDisabled() const { return m_hyphenationDisabled; }
 
     float availableWidth() const { return m_availableWidth; }
     float logicalLeftOffset() const { return m_logicalLeftOffset; }
-    const FlowContentsIterator::TextFragment& overflowedFragment() const { return m_overflowedFragment; }
-    bool hasTrailingWhitespace() const { return m_trailingWhitespaceLength; }
+    const TextFragmentIterator::TextFragment& overflowedFragment() const { return m_overflowedFragment; }
+    bool hasTrailingWhitespace() const { return m_lastFragment.type() == TextFragmentIterator::TextFragment::Whitespace; }
+    TextFragmentIterator::TextFragment lastFragment() const { return m_lastFragment; }
     bool isWhitespaceOnly() const { return m_trailingWhitespaceWidth && m_runsWidth == m_trailingWhitespaceWidth; }
     bool fits(float extra) const { return m_availableWidth >= m_runsWidth + extra; }
     bool firstCharacterFits() const { return m_firstCharacterFits; }
     float width() const { return m_runsWidth; }
+    std::pair<unsigned, bool> expansionOpportunityCount(unsigned from, unsigned to) const
+    {
+        ASSERT(m_fragments);
+        // linebreak runs are special.
+        if (from == to)
+            return std::make_pair(0, false);
+        unsigned expansionOpportunityCount = 0;
+        auto previousFragmentType = TextFragmentIterator::TextFragment::ContentEnd;
+        for (const auto& fragment : *m_fragments) {
+            if (fragment.end() <= from)
+                continue;
+            auto currentFragmentType = fragment.type();
+            auto expansionOpportunity = this->expansionOpportunity(currentFragmentType, previousFragmentType);
+            if (expansionOpportunity)
+                ++expansionOpportunityCount;
+            previousFragmentType = currentFragmentType;
+            if (fragment.end() >= to)
+                return std::make_pair(expansionOpportunityCount, expansionOpportunity);
+        }
+        ASSERT_NOT_REACHED();
+        return std::make_pair(expansionOpportunityCount, false);
+    }
 
-    void appendFragment(const FlowContentsIterator::TextFragment& fragment, Layout::RunVector& runs)
+    bool isEmpty() const
     {
-        // Adjust end position while collapsing.
-        unsigned endPosition = fragment.isCollapsed() ? fragment.start() + 1 : fragment.end();
+        if (!m_lastFragment.isValid())
+            return true;
+        if (!m_lastCompleteFragment.isEmpty())
+            return false;
+        return m_lastFragment.overlapsToNextRenderer();
+    }
 
-        if (m_createNewRun)
-            runs.append(Run(fragment.start(), endPosition, m_runsWidth, m_runsWidth + fragment.width(), false));
-        else {
-            ASSERT(runs.size());
-            Run& lastRun = runs.last();
-            lastRun.end = endPosition;
-            lastRun.logicalRight = m_runsWidth + fragment.width();
+    static inline unsigned endPositionForCollapsedFragment(const TextFragmentIterator::TextFragment& fragment)
+    {
+        return fragment.isCollapsed() ? fragment.start() + 1 : fragment.end();
+    }
+
+    void appendFragmentAndCreateRunIfNeeded(const TextFragmentIterator::TextFragment& fragment, Layout::RunVector& runs)
+    {
+        // Adjust end position while collapsing.
+        unsigned endPosition = endPositionForCollapsedFragment(fragment);
+        // New line needs new run.
+        if (!m_runsWidth) {
+            ASSERT(!m_uncompletedWidth);
+            runs.append(Run(fragment.start(), endPosition, m_runsWidth, m_runsWidth + fragment.width(), false, fragment.hasHyphen()));
+        } else {
+            // Advance last completed fragment when the previous fragment is all set (including multiple parts across renderers)
+            if ((m_lastFragment.type() != fragment.type()) || !m_lastFragment.overlapsToNextRenderer()) {
+                m_lastCompleteFragment = m_lastFragment;
+                m_uncompletedWidth = fragment.width();
+            } else
+                m_uncompletedWidth += fragment.width();
+            // Collapse neighbouring whitespace, if they are across multiple renderers and are not collapsed yet.
+            if (m_lastFragment.isCollapsible() && fragment.isCollapsible()) {
+                ASSERT(m_lastFragment.isLastInRenderer());
+                if (!m_lastFragment.isCollapsed()) {
+                    // Line width needs to be adjusted so that now it takes collapsing into consideration.
+                    m_runsWidth -= (m_lastFragment.width() - m_collapsedWhitespaceWidth);
+                }
+                // This fragment is collapsed completely. No run is needed.
+                return;
+            }
+            if (m_lastFragment.isLastInRenderer() || m_lastFragment.isCollapsed())
+                runs.append(Run(fragment.start(), endPosition, m_runsWidth, m_runsWidth + fragment.width(), false, fragment.hasHyphen()));
+            else {
+                Run& lastRun = runs.last();
+                lastRun.end = endPosition;
+                lastRun.logicalRight += fragment.width();
+                ASSERT(!lastRun.hasHyphen);
+                lastRun.hasHyphen = fragment.hasHyphen();
+            }
         }
-        m_createNewRun = fragment.isCollapsed();
         m_runsWidth += fragment.width();
+        m_lastFragment = fragment;
+        if (m_fragments)
+            (*m_fragments).append(fragment);
 
-        if (fragment.type() == FlowContentsIterator::TextFragment::Whitespace) {
-            m_trailingWhitespaceLength += endPosition - fragment.start();
+        if (fragment.type() == TextFragmentIterator::TextFragment::Whitespace)
             m_trailingWhitespaceWidth += fragment.width();
-        } else {
-            m_trailingWhitespaceLength = 0;
+        else {
             m_trailingWhitespaceWidth = 0;
+            m_lastNonWhitespaceFragment = fragment;
         }
 
         if (!m_firstCharacterFits)
             m_firstCharacterFits = fragment.start() + 1 > endPosition || m_runsWidth <= m_availableWidth;
     }
 
-    void removeTrailingWhitespace(Layout::RunVector& runs)
+    TextFragmentIterator::TextFragment revertToLastCompleteFragment(Layout::RunVector& runs)
     {
-        ASSERT(runs.size());
-        Run& lastRun = runs.last();
-        lastRun.logicalRight -= m_trailingWhitespaceWidth;
-        lastRun.end -= m_trailingWhitespaceLength;
-        if (lastRun.start == lastRun.end)
-            runs.removeLast();
+        if (!m_uncompletedWidth) {
+            ASSERT(m_lastFragment == m_lastCompleteFragment);
+            return m_lastFragment;
+        }
+        ASSERT(m_lastFragment.isValid());
+        m_runsWidth -= m_uncompletedWidth;
+        revertRuns(runs, endPositionForCollapsedFragment(m_lastCompleteFragment), m_uncompletedWidth);
+        m_uncompletedWidth = 0;
+        ASSERT(m_lastCompleteFragment.isValid());
+        return m_lastCompleteFragment;
+    }
 
-        m_runsWidth -= m_trailingWhitespaceWidth;
+    void removeTrailingWhitespace(Layout::RunVector& runs)
+    {
+        if (m_lastFragment.type() != TextFragmentIterator::TextFragment::Whitespace)
+            return;
+        if (m_lastNonWhitespaceFragment) {
+            auto needsReverting = m_lastNonWhitespaceFragment->end() != m_lastFragment.end();
+            // Trailing whitespace fragment might actually have zero length.
+            ASSERT(needsReverting || !m_trailingWhitespaceWidth);
+            if (needsReverting) {
+                revertRuns(runs, m_lastNonWhitespaceFragment->end(), m_trailingWhitespaceWidth);
+                m_runsWidth -= m_trailingWhitespaceWidth;
+            }
+            m_trailingWhitespaceWidth = 0;
+            m_lastFragment = *m_lastNonWhitespaceFragment;
+            return;
+        }
+        // This line is all whitespace.
+        revertAllRunsOnCurrentLine(runs);
+        m_runsWidth = 0;
         m_trailingWhitespaceWidth = 0;
-        m_trailingWhitespaceLength = 0;
+        // FIXME: Make m_lastFragment optional.
+        m_lastFragment = TextFragmentIterator::TextFragment();
     }
 
 private:
+    bool expansionOpportunity(TextFragmentIterator::TextFragment::Type currentFragmentType, TextFragmentIterator::TextFragment::Type previousFragmentType) const
+    {
+        return (currentFragmentType == TextFragmentIterator::TextFragment::Whitespace
+            || (currentFragmentType == TextFragmentIterator::TextFragment::NonWhitespace && previousFragmentType == TextFragmentIterator::TextFragment::NonWhitespace));
+    }
+
     float m_availableWidth { 0 };
     float m_logicalLeftOffset { 0 };
-    FlowContentsIterator::TextFragment m_overflowedFragment;
     float m_runsWidth { 0 };
-    bool m_createNewRun { true };
+    TextFragmentIterator::TextFragment m_overflowedFragment;
+    TextFragmentIterator::TextFragment m_lastFragment;
+    std::optional<TextFragmentIterator::TextFragment> m_lastNonWhitespaceFragment;
+    TextFragmentIterator::TextFragment m_lastCompleteFragment;
+    float m_uncompletedWidth { 0 };
     float m_trailingWhitespaceWidth { 0 }; // Use this to remove trailing whitespace without re-mesuring the text.
-    unsigned m_trailingWhitespaceLength { 0 };
+    float m_collapsedWhitespaceWidth { 0 };
     // Having one character on the line does not necessarily mean it actually fits.
     // First character of the first fragment might be forced on to the current line even if it does not fit.
     bool m_firstCharacterFits { false };
+    bool m_hyphenationDisabled { false };
+    std::optional<Vector<TextFragmentIterator::TextFragment, 30>> m_fragments;
 };
 
-class FragmentForwardIterator : public std::iterator<std::forward_iterator_tag, unsigned> {
-public:
-    FragmentForwardIterator(unsigned fragmentIndex)
-        : m_fragmentIndex(fragmentIndex)
-    {
-    }
-
-    FragmentForwardIterator& operator++()
-    {
-        ++m_fragmentIndex;
-        return *this;
-    }
-
-    bool operator!=(const FragmentForwardIterator& other) const { return m_fragmentIndex != other.m_fragmentIndex; }
-    unsigned operator*() const { return m_fragmentIndex; }
-
-private:
-    unsigned m_fragmentIndex { 0 };
-};
-
-static FragmentForwardIterator begin(const FlowContentsIterator::TextFragment& fragment)  { return FragmentForwardIterator(fragment.start()); }
-static FragmentForwardIterator end(const FlowContentsIterator::TextFragment& fragment)  { return FragmentForwardIterator(fragment.end()); }
-
-static bool preWrap(const FlowContentsIterator::Style& style)
+static bool preWrap(const TextFragmentIterator::Style& style)
 {
     return style.wrapLines && !style.collapseWhitespace;
 }
     
-static void removeTrailingWhitespace(LineState& lineState, Layout::RunVector& runs, const FlowContentsIterator& flowContentsIterator)
+static void removeTrailingWhitespace(LineState& lineState, Layout::RunVector& runs, const TextFragmentIterator& textFragmentIterator)
 {
     if (!lineState.hasTrailingWhitespace())
         return;
-    
-    // Remove collapsed whitespace, or non-collapsed pre-wrap whitespace, unless it's the only content on the line -so removing the whitesapce would produce an empty line.
-    const auto& style = flowContentsIterator.style();
+    // Remove collapsed whitespace, or non-collapsed pre-wrap whitespace, unless it's the only content on the line -so removing the whitesapce
+    // would produce an empty line.
+    const auto& style = textFragmentIterator.style();
     bool collapseWhitespace = style.collapseWhitespace | preWrap(style);
     if (!collapseWhitespace)
         return;
-
     if (preWrap(style) && lineState.isWhitespaceOnly())
         return;
-
     lineState.removeTrailingWhitespace(runs);
 }
 
-static void updateLineConstrains(const RenderBlockFlow& flow, LineState& line)
+static void updateLineConstrains(const RenderBlockFlow& flow, LineState& line, const LineState& previousLine, unsigned& numberOfPrecedingLinesWithHyphen, const TextFragmentIterator::Style& style, bool isFirstLine)
 {
+    bool shouldApplyTextIndent = !flow.isAnonymous() || flow.parent()->firstChild() == &flow;
     LayoutUnit height = flow.logicalHeight();
-    LayoutUnit logicalHeight = flow.minLineHeightForReplacedRenderer(false, 0);
-    float logicalRightOffset = flow.logicalRightOffsetForLine(height, false, logicalHeight);
-    line.setLogicalLeftOffset(flow.logicalLeftOffsetForLine(height, false, logicalHeight));
+    line.setLogicalLeftOffset(flow.logicalLeftOffsetForLine(height, DoNotIndentText) + (shouldApplyTextIndent && isFirstLine ? flow.textIndentOffset() : LayoutUnit(0)));
+    float logicalRightOffset = flow.logicalRightOffsetForLine(height, DoNotIndentText);
     line.setAvailableWidth(std::max<float>(0, logicalRightOffset - line.logicalLeftOffset()));
+    if (style.textAlign == JUSTIFY)
+        line.setNeedsAllFragments();
+    numberOfPrecedingLinesWithHyphen = (previousLine.isEmpty() || !previousLine.lastFragment().hasHyphen()) ? 0 : numberOfPrecedingLinesWithHyphen + 1;
+    if (style.hyphenLimitLines && numberOfPrecedingLinesWithHyphen >= *style.hyphenLimitLines)
+        line.setHyphenationDisabled();
+    line.setCollapedWhitespaceWidth(style.font.spaceWidth() + style.wordSpacing);
 }
 
-static FlowContentsIterator::TextFragment splitFragmentToFitLine(FlowContentsIterator::TextFragment& fragmentToSplit, float availableWidth, bool keepAtLeastOneCharacter, const FlowContentsIterator& flowContentsIterator)
+struct SplitFragmentData {
+    unsigned position;
+    float width;
+};
+static std::optional<unsigned> hyphenPositionForFragment(SplitFragmentData splitData, const TextFragmentIterator::TextFragment& fragmentToSplit,
+    const LineState& line, const TextFragmentIterator& textFragmentIterator, float availableWidth)
 {
-    // FIXME: add surrogate pair support.
+    auto& style = textFragmentIterator.style();
+    if (!style.shouldHyphenate || line.isHyphenationDisabled())
+        return std::nullopt;
+
+    // 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 = availableWidth - style.hyphenStringWidth;
+    if (!line.isEmpty())
+        adjustedAvailableWidth += style.font.spaceWidth();
+    if (!enoughWidthForHyphenation(adjustedAvailableWidth, style.font.pixelSize()))
+        return std::nullopt;
+
+    // We might be able to fit the hyphen at the split position.
+    auto splitPositionWithHyphen = splitData.position;
+    // Find a splitting position where hyphen surely fits.
     unsigned start = fragmentToSplit.start();
-    auto it = std::upper_bound(begin(fragmentToSplit), end(fragmentToSplit), availableWidth, [&flowContentsIterator, start](float availableWidth, unsigned index) {
-        // FIXME: use the actual left position of the line (instead of 0) to calculated width. It might give false width for tab characters.
-        return availableWidth < flowContentsIterator.textWidth(start, index + 1, 0);
-    });
-    unsigned splitPosition = (*it);
-    if (keepAtLeastOneCharacter && splitPosition == fragmentToSplit.start())
-        ++splitPosition;
-    return fragmentToSplit.split(splitPosition, flowContentsIterator);
+    auto leftSideWidth = splitData.width;
+    while (leftSideWidth + style.hyphenStringWidth > availableWidth) {
+        if (--splitPositionWithHyphen <= start)
+            return std::nullopt; // No space for hyphen.
+        leftSideWidth -= textFragmentIterator.textWidth(splitPositionWithHyphen, splitPositionWithHyphen + 1, 0);
+    }
+    ASSERT(splitPositionWithHyphen > start);
+    return textFragmentIterator.lastHyphenPosition(fragmentToSplit, splitPositionWithHyphen + 1);
+}
+
+static SplitFragmentData split(const TextFragmentIterator::TextFragment& fragment, float availableWidth,
+    const TextFragmentIterator& textFragmentIterator)
+{
+    ASSERT(availableWidth >= 0);
+    auto left = fragment.start();
+    // Pathological case of (extremely)long string and narrow lines.
+    // Adjust the range so that we can pick a reasonable midpoint.
+    auto averageCharacterWidth = fragment.width() / fragment.length();
+    auto right = std::min<unsigned>(left + (2 * availableWidth / averageCharacterWidth), fragment.end() - 1);
+    // Preserve the left width for the final split position so that we don't need to remeasure the left side again.
+    float leftSideWidth = 0;
+    while (left < right) {
+        auto middle = (left + right) / 2;
+        auto width = textFragmentIterator.textWidth(fragment.start(), middle + 1, 0);
+        if (width < availableWidth) {
+            left = middle + 1;
+            leftSideWidth = width;
+        } else if (width > availableWidth)
+            right = middle;
+        else {
+            right = middle + 1;
+            leftSideWidth = width;
+            break;
+        }
+    }
+    return { right, leftSideWidth };
 }
 
-static FlowContentsIterator::TextFragment firstFragment(FlowContentsIterator& flowContentsIterator, const LineState& previousLine)
+static TextFragmentIterator::TextFragment splitFragmentToFitLine(TextFragmentIterator::TextFragment& fragmentToSplit,
+    const LineState& line, const TextFragmentIterator& textFragmentIterator)
 {
-    // Handle overflowed fragment from previous line.
-    FlowContentsIterator::TextFragment firstFragment(previousLine.overflowedFragment());
-    const auto& style = flowContentsIterator.style();
-
-    if (firstFragment.isEmpty())
-        firstFragment = flowContentsIterator.nextTextFragment();
-    else {
-        // Special overflow pre-wrap whitespace handling: ignore the overflowed whitespace if we managed to fit at least one character on the previous line.
-        // When the line is too short to fit one character (thought it still stays on the line) we continue with the overflow whitespace content on this line.
-        if (firstFragment.type() == FlowContentsIterator::TextFragment::Whitespace && preWrap(style) && previousLine.firstCharacterFits()) {
-            firstFragment = flowContentsIterator.nextTextFragment();
-            // If skipping the whitespace puts us on a hard newline, skip the newline too as we already wrapped the line.
-            if (firstFragment.type() == FlowContentsIterator::TextFragment::LineBreak)
-                firstFragment = flowContentsIterator.nextTextFragment();
+    auto availableWidth = line.availableWidth() - line.width();
+    auto splitFragmentData = split(fragmentToSplit, availableWidth, textFragmentIterator);
+    std::optional<unsigned> hyphenPosition = std::nullopt;
+    // Does first character fit this line?
+    if (splitFragmentData.position == fragmentToSplit.start()) {
+        // Keep at least one character on empty lines.
+        if (line.isEmpty())
+            splitFragmentData.width = textFragmentIterator.textWidth(fragmentToSplit.start(), ++splitFragmentData.position, 0);
+    } else {
+        hyphenPosition = hyphenPositionForFragment(splitFragmentData, fragmentToSplit, line, textFragmentIterator, availableWidth);
+        if (hyphenPosition) {
+            splitFragmentData.position = *hyphenPosition;
+            splitFragmentData.width = textFragmentIterator.textWidth(fragmentToSplit.start(), splitFragmentData.position, 0);
         }
     }
+    // If the right side surely does not fit the (next)line, we don't need the width to be kerning/ligature adjusted.
+    // Part of it gets re-measured as the left side during next split.
+    // This saves measuring long chunk of text repeatedly (see pathological case at ::split).
+    auto rightSideWidth = fragmentToSplit.width() - splitFragmentData.width;
+    if (rightSideWidth < 2 * availableWidth)
+        rightSideWidth = textFragmentIterator.textWidth(splitFragmentData.position, fragmentToSplit.end(), 0);
+    return hyphenPosition ? fragmentToSplit.splitWithHyphen(splitFragmentData.position, textFragmentIterator.style().hyphenStringWidth,
+        splitFragmentData.width, rightSideWidth) : fragmentToSplit.split(splitFragmentData.position, splitFragmentData.width, rightSideWidth);
+}
+
+enum PreWrapLineBreakRule { Preserve, Ignore };
+
+static TextFragmentIterator::TextFragment consumeLineBreakIfNeeded(const TextFragmentIterator::TextFragment& fragment, TextFragmentIterator& textFragmentIterator, LineState& line, Layout::RunVector& runs,
+    PreWrapLineBreakRule preWrapLineBreakRule = PreWrapLineBreakRule::Preserve)
+{
+    if (!fragment.isLineBreak())
+        return fragment;
+
+    bool isHardLinebreak = fragment.type() == TextFragmentIterator::TextFragment::HardLineBreak;
+    // <br> always produces a run. (required by testing output)
+    if (isHardLinebreak)
+        line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
+
+    auto& style = textFragmentIterator.style();
+    if (style.preserveNewline && preWrapLineBreakRule == PreWrapLineBreakRule::Preserve) {
+        if (!isHardLinebreak)
+            return fragment;
+    }
+    return textFragmentIterator.nextTextFragment();
+}
+
+static TextFragmentIterator::TextFragment skipWhitespaceIfNeeded(const TextFragmentIterator::TextFragment& fragment, TextFragmentIterator& textFragmentIterator)
+{
+    if (!textFragmentIterator.style().collapseWhitespace)
+        return fragment;
 
-    // Check if we need to skip the leading whitespace.
-    if (style.collapseWhitespace && firstFragment.type() == FlowContentsIterator::TextFragment::Whitespace)
-        firstFragment = flowContentsIterator.nextTextFragment();
-    return firstFragment;
+    TextFragmentIterator::TextFragment firstNonWhitespaceFragment = fragment;
+    while (firstNonWhitespaceFragment.type() == TextFragmentIterator::TextFragment::Whitespace)
+        firstNonWhitespaceFragment = textFragmentIterator.nextTextFragment();
+    return firstNonWhitespaceFragment;
 }
 
-static bool createLineRuns(LineState& line, const LineState& previousLine, Layout::RunVector& runs, FlowContentsIterator& flowContentsIterator)
+static TextFragmentIterator::TextFragment firstFragment(TextFragmentIterator& textFragmentIterator, LineState& currentLine, const LineState& previousLine, Layout::RunVector& runs)
 {
-    const auto& style = flowContentsIterator.style();
-    bool lineCanBeWrapped = style.wrapLines || style.breakWordOnOverflow;
-    auto fragment = firstFragment(flowContentsIterator, previousLine);
-    while (fragment.type() != FlowContentsIterator::TextFragment::ContentEnd) {
-        // Hard linebreak.
-        if (fragment.type() == FlowContentsIterator::TextFragment::LineBreak) {
+    // Handle overflow fragment from previous line.
+    auto overflowedFragment = previousLine.overflowedFragment();
+    if (overflowedFragment.isEmpty())
+        return skipWhitespaceIfNeeded(textFragmentIterator.nextTextFragment(), textFragmentIterator);
+
+    if (overflowedFragment.type() != TextFragmentIterator::TextFragment::Whitespace)
+        return overflowedFragment;
+
+    // Leading whitespace handling.
+    auto& style = textFragmentIterator.style();
+    // Special overflow pre-wrap whitespace handling: skip the overflowed whitespace (even when style says not-collapsible)
+    // if we manage to fit at least one character on the previous line.
+    auto preWrapIsOn = preWrap(style);
+    if ((style.collapseWhitespace || preWrapIsOn) && previousLine.firstCharacterFits()) {
+        // If skipping the whitespace puts us on a newline, skip the newline too as we already wrapped the line.
+        auto firstFragmentCandidate = consumeLineBreakIfNeeded(textFragmentIterator.nextTextFragment(), textFragmentIterator, currentLine, runs,
+            preWrapIsOn ? PreWrapLineBreakRule::Ignore : PreWrapLineBreakRule::Preserve);
+        return skipWhitespaceIfNeeded(firstFragmentCandidate, textFragmentIterator);
+    }
+    return skipWhitespaceIfNeeded(overflowedFragment, textFragmentIterator);
+}
+
+static void forceFragmentToLine(LineState& line, TextFragmentIterator& textFragmentIterator, Layout::RunVector& runs, const TextFragmentIterator::TextFragment& fragment)
+{
+    line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
+    // Check if there are more fragments to add to the current line.
+    auto nextFragment = textFragmentIterator.nextTextFragment();
+    if (fragment.overlapsToNextRenderer()) {
+        while (true) {
+            if (nextFragment.type() != fragment.type())
+                break;
+            line.appendFragmentAndCreateRunIfNeeded(nextFragment, runs);
+            // Does it overlap to the next segment?
+            if (!nextFragment.overlapsToNextRenderer())
+                return;
+            nextFragment = textFragmentIterator.nextTextFragment();
+        }
+    }
+    // When the forced fragment is followed by either whitespace and/or line break, consume them too, otherwise we end up with an extra whitespace and/or line break.
+    nextFragment = skipWhitespaceIfNeeded(nextFragment, textFragmentIterator);
+    nextFragment = consumeLineBreakIfNeeded(nextFragment, textFragmentIterator, line, runs);
+    line.setOverflowedFragment(nextFragment);
+}
+
+static bool createLineRuns(LineState& line, const LineState& previousLine, Layout::RunVector& runs, TextFragmentIterator& textFragmentIterator)
+{
+    const auto& style = textFragmentIterator.style();
+    bool lineCanBeWrapped = style.wrapLines || style.breakFirstWordOnOverflow || style.breakAnyWordOnOverflow;
+    auto fragment = firstFragment(textFragmentIterator, line, previousLine, runs);
+    while (fragment.type() != TextFragmentIterator::TextFragment::ContentEnd) {
+        // Hard and soft linebreaks.
+        if (fragment.isLineBreak()) {
             // Add the new line fragment only if there's nothing on the line. (otherwise the extra new line character would show up at the end of the content.)
-            if (!line.width())
-                line.appendFragment(fragment, runs);
+            if (line.isEmpty() || fragment.type() == TextFragmentIterator::TextFragment::HardLineBreak) {
+                if (style.textAlign == RIGHT || style.textAlign == WEBKIT_RIGHT)
+                    line.removeTrailingWhitespace(runs);
+                line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
+            }
             break;
         }
         if (lineCanBeWrapped && !line.fits(fragment.width())) {
@@ -408,80 +800,139 @@ static bool createLineRuns(LineState& line, const LineState& previousLine, Layou
             // 1. Whitesapce collapse on: whitespace is skipped. Jump to next line.
             // 2. Whitespace collapse off: whitespace is wrapped.
             // 3. First, non-whitespace fragment is either wrapped or kept on the line. (depends on overflow-wrap)
-            // 4. Non-whitespace fragment when there's already another fragment on the line gets pushed to the next line.
-            bool emptyLine = !line.width();
+            // 5. Non-whitespace fragment when there's already another fragment on the line either gets wrapped (word-break: break-all)
+            // or gets pushed to the next line.
+            bool emptyLine = line.isEmpty();
             // Whitespace fragment.
-            if (fragment.type() == FlowContentsIterator::TextFragment::Whitespace) {
-                if (!style.collapseWhitespace) {
-                    // Split the fragment; (modified)fragment stays on this line, overflowedFragment is pushed to next line.
-                    line.setOverflowedFragment(splitFragmentToFitLine(fragment, line.availableWidth() - line.width(), emptyLine, flowContentsIterator));
-                    line.appendFragment(fragment, runs);
+            if (fragment.type() == TextFragmentIterator::TextFragment::Whitespace) {
+                if (style.collapseWhitespace) {
+                    // Push collapased whitespace to the next line.
+                    line.setOverflowedFragment(fragment);
+                    break;
                 }
-                // When whitespace collapse is on, whitespace that doesn't fit is simply skipped.
+                // Split the whitespace; left part stays on this line, right is pushed to next line.
+                line.setOverflowedFragment(splitFragmentToFitLine(fragment, line, textFragmentIterator));
+                line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
                 break;
             }
             // Non-whitespace fragment. (!style.wrapLines: bug138102(preserve existing behavior)
-            if ((emptyLine && style.breakWordOnOverflow) || !style.wrapLines) {
+            if (((emptyLine && style.breakFirstWordOnOverflow) || style.breakAnyWordOnOverflow) || !style.wrapLines) {
                 // Split the fragment; (modified)fragment stays on this line, overflowedFragment is pushed to next line.
-                line.setOverflowedFragment(splitFragmentToFitLine(fragment, line.availableWidth() - line.width(), emptyLine, flowContentsIterator));
-                line.appendFragment(fragment, runs);
+                line.setOverflowedFragment(splitFragmentToFitLine(fragment, line, textFragmentIterator));
+                line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
                 break;
             }
+            ASSERT(fragment.type() == TextFragmentIterator::TextFragment::NonWhitespace);
+            // Find out if this non-whitespace fragment has a hyphen where we can break.
+            if (style.shouldHyphenate) {
+                auto fragmentToSplit = fragment;
+                // Split and check if we actually ended up with a hyphen.
+                auto overflowFragment = splitFragmentToFitLine(fragmentToSplit, line, textFragmentIterator);
+                if (fragmentToSplit.hasHyphen()) {
+                    line.setOverflowedFragment(overflowFragment);
+                    line.appendFragmentAndCreateRunIfNeeded(fragmentToSplit, runs);
+                    break;
+                }
+                // No hyphen, no split.
+            }
             // Non-breakable non-whitespace first fragment. Add it to the current line. -it overflows though.
             if (emptyLine) {
-                line.appendFragment(fragment, runs);
+                forceFragmentToLine(line, textFragmentIterator, runs, fragment);
                 break;
             }
             // Non-breakable non-whitespace fragment when there's already content on the line. Push it to the next line.
+            ASSERT(line.lastFragment().isValid());
+            if (line.lastFragment().overlapsToNextRenderer()) {
+                // Check if this fragment is a continuation of a previous segment. In such cases, we need to remove them all.
+                textFragmentIterator.revertToEndOfFragment(line.revertToLastCompleteFragment(runs));
+                break;
+            }
             line.setOverflowedFragment(fragment);
             break;
         }
-        line.appendFragment(fragment, runs);
+        line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
         // Find the next text fragment.
-        fragment = flowContentsIterator.nextTextFragment(line.width());
+        fragment = textFragmentIterator.nextTextFragment(line.width());
     }
-    return fragment.type() == FlowContentsIterator::TextFragment::ContentEnd && line.overflowedFragment().isEmpty();
+    return (fragment.type() == TextFragmentIterator::TextFragment::ContentEnd && line.overflowedFragment().isEmpty()) || line.overflowedFragment().type() == TextFragmentIterator::TextFragment::ContentEnd;
 }
 
-static void closeLineEndingAndAdjustRuns(LineState& line, Layout::RunVector& runs, unsigned previousRunCount, unsigned& lineCount, const FlowContentsIterator& flowContentsIterator)
+static ExpansionBehavior expansionBehavior(bool isAfterExpansion, bool lastRunOnLine)
+{
+    ExpansionBehavior expansionBehavior;
+    expansionBehavior = isAfterExpansion ? ForbidLeadingExpansion : AllowLeadingExpansion;
+    expansionBehavior |= lastRunOnLine ? ForbidTrailingExpansion : AllowTrailingExpansion;
+    return expansionBehavior;
+}
+
+static void justifyRuns(const LineState& line, Layout::RunVector& runs, unsigned firstRunIndex)
 {
-    if (previousRunCount == runs.size())
-        return;
     ASSERT(runs.size());
-    removeTrailingWhitespace(line, runs, flowContentsIterator);
-    if (!runs.size())
+    auto widthToDistribute = line.availableWidth() - line.width();
+    if (widthToDistribute <= 0)
         return;
-    // Adjust runs' position by taking line's alignment into account.
-    if (float lineLogicalLeft = computeLineLeft(flowContentsIterator.style().textAlign, line.availableWidth(), line.width(), line.logicalLeftOffset())) {
-        for (unsigned i = previousRunCount; i < runs.size(); ++i) {
-            runs[i].logicalLeft += lineLogicalLeft;
-            runs[i].logicalRight += lineLogicalLeft;
-        }
+
+    auto lastRunIndex = runs.size() - 1;
+    ASSERT(firstRunIndex <= lastRunIndex);
+    Vector<std::pair<unsigned, ExpansionBehavior>> expansionOpportunityList;
+    unsigned expansionOpportunityCountOnThisLine = 0;
+    auto isAfterExpansion = true;
+    for (auto i = firstRunIndex; i <= lastRunIndex; ++i) {
+        const auto& run = runs.at(i);
+        unsigned opportunityCountInRun = 0;
+        std::tie(opportunityCountInRun, isAfterExpansion) = line.expansionOpportunityCount(run.start, run.end);
+        expansionOpportunityList.append(std::make_pair(opportunityCountInRun, expansionBehavior(isAfterExpansion, i == lastRunIndex)));
+        expansionOpportunityCountOnThisLine += opportunityCountInRun;
     }
-    runs.last().isEndOfLine = true;
-    ++lineCount;
+    if (!expansionOpportunityCountOnThisLine)
+        return;
+
+    ASSERT(expansionOpportunityList.size() == lastRunIndex - firstRunIndex + 1);
+    auto expansion = widthToDistribute / expansionOpportunityCountOnThisLine;
+    float accumulatedExpansion = 0;
+    for (auto i = firstRunIndex; i <= lastRunIndex; ++i) {
+        auto& run = runs.at(i);
+        unsigned opportunityCountInRun;
+        std::tie(opportunityCountInRun, run.expansionBehavior) = expansionOpportunityList.at(i - firstRunIndex);
+        run.expansion = opportunityCountInRun * expansion;
+        run.logicalLeft += accumulatedExpansion;
+        run.logicalRight += (accumulatedExpansion + run.expansion);
+        accumulatedExpansion += run.expansion;
+    }
+}
+
+static ETextAlign textAlignForLine(const TextFragmentIterator::Style& style, bool lastLine)
+{
+    // Fallback to LEFT (START) alignment for non-collapsable content and for the last line before a forced break or the end of the block.
+    auto textAlign = style.textAlign;
+    if (textAlign == JUSTIFY && (!style.collapseWhitespace || lastLine))
+        textAlign = LEFT;
+    return textAlign;
 }
 
-static void splitRunsAtRendererBoundary(Layout::RunVector& lineRuns, const FlowContentsIterator& flowContentsIterator)
+static void closeLineEndingAndAdjustRuns(LineState& line, Layout::RunVector& runs, std::optional<unsigned> lastRunIndexOfPreviousLine, unsigned& lineCount,
+    const TextFragmentIterator& textFragmentIterator, bool lastLineInFlow)
 {
-    // FIXME: We should probably split during run construction instead of as a separate pass.
-    if (lineRuns.isEmpty())
+    if (!runs.size() || (lastRunIndexOfPreviousLine && runs.size() - 1 == lastRunIndexOfPreviousLine.value()))
         return;
-    unsigned runIndex = 0;
-    do {
-        const Run& run = lineRuns.at(runIndex);
-        ASSERT(run.start != run.end);
-        auto& startSegment = flowContentsIterator.segmentForPosition(run.start);
-        if (run.end <= startSegment.end)
-            continue;
-        // This run overlaps multiple renderers. Split it up.
-        // Split run at the renderer's boundary and create a new run for the left side, while use the current run as the right side.
-        float logicalRightOfLeftRun = run.logicalLeft + flowContentsIterator.textWidth(run.start, startSegment.end, run.logicalLeft);
-        lineRuns.insert(runIndex, Run(run.start, startSegment.end, run.logicalLeft, logicalRightOfLeftRun, false));
-        Run& rightSideRun = lineRuns.at(runIndex + 1);
-        rightSideRun.start = startSegment.end;
-        rightSideRun.logicalLeft = logicalRightOfLeftRun;
-    } while (++runIndex < lineRuns.size());
+    removeTrailingWhitespace(line, runs, textFragmentIterator);
+    if (!runs.size())
+        return;
+    // Adjust runs' position by taking line's alignment into account.
+    const auto& style = textFragmentIterator.style();
+    auto firstRunIndex = lastRunIndexOfPreviousLine ? lastRunIndexOfPreviousLine.value() + 1 : 0;
+    auto lineLogicalLeft = line.logicalLeftOffset();
+    auto textAlign = textAlignForLine(style, lastLineInFlow || (line.lastFragment().isValid() && line.lastFragment().type() == TextFragmentIterator::TextFragment::HardLineBreak));
+    if (textAlign == JUSTIFY)
+        justifyRuns(line, runs, firstRunIndex);
+    else
+        lineLogicalLeft = computeLineLeft(textAlign, line.availableWidth(), line.width(), line.logicalLeftOffset());
+    for (auto i = firstRunIndex; i < runs.size(); ++i) {
+        runs[i].logicalLeft += lineLogicalLeft;
+        runs[i].logicalRight += lineLogicalLeft;
+    }
+    runs.last().isEndOfLine = true;
+    ++lineCount;
 }
 
 static void createTextRuns(Layout::RunVector& runs, RenderBlockFlow& flow, unsigned& lineCount)
@@ -489,33 +940,27 @@ static void createTextRuns(Layout::RunVector& runs, RenderBlockFlow& flow, unsig
     LayoutUnit borderAndPaddingBefore = flow.borderAndPaddingBefore();
     LayoutUnit lineHeight = lineHeightFromFlow(flow);
     LineState line;
+    unsigned numberOfPrecedingLinesWithHyphen = 0;
     bool isEndOfContent = false;
-    FlowContentsIterator flowContentsIterator = FlowContentsIterator(flow);
-
+    TextFragmentIterator textFragmentIterator = TextFragmentIterator(flow);
+    std::optional<unsigned> lastRunIndexOfPreviousLine;
     do {
         flow.setLogicalHeight(lineHeight * lineCount + borderAndPaddingBefore);
         LineState previousLine = line;
-        unsigned previousRunCount = runs.size();
         line = LineState();
-        updateLineConstrains(flow, line);
-        isEndOfContent = createLineRuns(line, previousLine, runs, flowContentsIterator);
-        closeLineEndingAndAdjustRuns(line, runs, previousRunCount, lineCount, flowContentsIterator);
+        updateLineConstrains(flow, line, previousLine, numberOfPrecedingLinesWithHyphen, textFragmentIterator.style(), !lineCount);
+        isEndOfContent = createLineRuns(line, previousLine, runs, textFragmentIterator);
+        closeLineEndingAndAdjustRuns(line, runs, lastRunIndexOfPreviousLine, lineCount, textFragmentIterator, isEndOfContent);
+        if (runs.size())
+            lastRunIndexOfPreviousLine = runs.size() - 1;
     } while (!isEndOfContent);
-
-    if (flow.firstChild() != flow.lastChild())
-        splitRunsAtRendererBoundary(runs, flowContentsIterator);
 }
 
 std::unique_ptr<Layout> create(RenderBlockFlow& flow)
 {
     unsigned lineCount = 0;
     Layout::RunVector runs;
-
     createTextRuns(runs, flow, lineCount);
-    for (auto& renderer : childrenOfType<RenderObject>(flow)) {
-        ASSERT(is<RenderText>(renderer));
-        renderer.clearNeedsLayout();
-    }
     return Layout::create(runs, lineCount);
 }