Simple line layout: Introduce text fragment continuation.
[WebKit-https.git] / Source / WebCore / rendering / SimpleLineLayout.cpp
index 1786465..f2c3345 100644 (file)
@@ -29,6 +29,7 @@
 #include "FontCache.h"
 #include "Frame.h"
 #include "GraphicsContext.h"
+#include "HTMLTextFormControlElement.h"
 #include "HitTestLocation.h"
 #include "HitTestRequest.h"
 #include "HitTestResult.h"
 #include "LineWidth.h"
 #include "PaintInfo.h"
 #include "RenderBlockFlow.h"
+#include "RenderChildIterator.h"
 #include "RenderStyle.h"
 #include "RenderText.h"
+#include "RenderTextControl.h"
 #include "RenderView.h"
 #include "Settings.h"
-#include "SimpleLineLayoutResolver.h"
+#include "SimpleLineLayoutFlowContents.h"
+#include "SimpleLineLayoutFunctions.h"
 #include "Text.h"
 #include "TextPaintStyle.h"
-#include "break_lines.h"
-#include <wtf/unicode/Unicode.h>
 
 namespace WebCore {
 namespace SimpleLineLayout {
@@ -52,6 +54,9 @@ namespace SimpleLineLayout {
 template <typename CharacterType>
 static bool canUseForText(const CharacterType* text, unsigned length, const SimpleFontData& fontData)
 {
+    // FIXME: <textarea maxlength=0> generates empty text node.
+    if (!length)
+        return false;
     for (unsigned i = 0; i < length; ++i) {
         UChar character = text[i];
         if (character == ' ')
@@ -85,43 +90,35 @@ static bool canUseForText(const RenderText& textRenderer, const SimpleFontData&
 
 bool canUseFor(const RenderBlockFlow& flow)
 {
-#if !PLATFORM(MAC) && !PLATFORM(GTK) && !PLATFORM(EFL)
-    // FIXME: Non-mac platforms are hitting ASSERT(run.charactersLength() >= run.length())
-    // https://bugs.webkit.org/show_bug.cgi?id=123338
-    return false;
-#endif
     if (!flow.frame().settings().simpleLineLayoutEnabled())
         return false;
     if (!flow.firstChild())
         return false;
-    // This currently covers <blockflow>#text</blockflow> case.
+    // 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.
-    if (flow.firstChild() != flow.lastChild())
-        return false;
-    if (!flow.firstChild()->isText())
-        return false;
-    // Supporting floats would be very beneficial.
-    if (flow.containsFloats())
-        return false;
+    for (const auto& renderer : childrenOfType<RenderObject>(flow)) {
+        if (!is<RenderText>(renderer) || !downcast<RenderText>(renderer).text()->is8Bit())
+            return false;
+    }
     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;
-    // These tests only works during layout. Outside layout this function may give false positives.
-    if (flow.view().layoutState()) {
-#if ENABLE(CSS_SHAPES)
-        if (flow.view().layoutState()->shapeInsideInfo())
-            return false;
-#endif
-        if (flow.view().layoutState()->m_columnInfo)
-            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;
@@ -130,14 +127,9 @@ bool canUseFor(const RenderBlockFlow& flow)
     // Non-visible overflow should be pretty easy to support.
     if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE)
         return false;
-    // Pre/no-wrap would be very helpful to support.
-    if (style.whiteSpace() != NORMAL)
-        return false;
     if (!style.textIndent().isZero())
         return false;
-    if (style.wordSpacing() || style.letterSpacing())
-        return false;
-    if (style.textTransform() != TTNONE)
+    if (!style.wordSpacing().isZero() || style.letterSpacing())
         return false;
     if (!style.isLeftToRightDirection())
         return false;
@@ -159,32 +151,33 @@ bool canUseFor(const RenderBlockFlow& flow)
         return false;
     if (style.textShadow())
         return false;
-#if ENABLE(CSS_SHAPES)
-    if (style.resolvedShapeInside())
-        return true;
-#endif
     if (style.textOverflow() || (flow.isAnonymousBlock() && flow.parent()->style().textOverflow()))
         return false;
     if (style.hasPseudoStyle(FIRST_LINE) || style.hasPseudoStyle(FIRST_LETTER))
         return false;
     if (style.hasTextCombine())
         return false;
-    if (style.overflowWrap() != NormalOverflowWrap)
-        return false;
     if (style.backgroundClip() == TextFillBox)
         return false;
     if (style.borderFit() == BorderFitLines)
         return false;
-    const RenderText& textRenderer = toRenderText(*flow.firstChild());
+    const RenderText& textRenderer = downcast<RenderText>(*flow.firstChild());
+    if (flow.containsFloats()) {
+        // 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.
+        float minimumWidthNeeded = textRenderer.minLogicalWidth();
+        for (auto& floatRenderer : *flow.floatingObjectSet()) {
+            ASSERT(floatRenderer);
+            float availableWidth = flow.availableLogicalWidthForLine(floatRenderer->y(), false);
+            if (availableWidth < minimumWidthNeeded)
+                return false;
+        }
+    }
     if (textRenderer.isCombineText() || textRenderer.isCounter() || textRenderer.isQuote() || textRenderer.isTextFragment()
-#if ENABLE(SVG)
-        || textRenderer.isSVGInlineText()
-#endif
-        )
+        || textRenderer.isSVGInlineText())
         return false;
     if (style.font().codePath(TextRun(textRenderer.text())) != Font::Simple)
         return false;
-    if (style.font().isSVGFont())
+    if (style.font().primaryFont()->isSVGFont())
         return false;
 
     // We assume that all lines have metrics based purely on the primary font.
@@ -197,162 +190,467 @@ bool canUseFor(const RenderBlockFlow& flow)
     return true;
 }
 
-static inline bool isWhitespace(UChar character)
-{
-    return character == ' ' || character == '\t' || character == '\n';
-}
-
-template <typename CharacterType>
-static inline unsigned skipWhitespaces(const CharacterType* text, unsigned offset, unsigned length)
-{
-    for (; offset < length; ++offset) {
-        if (!isWhitespace(text[offset]))
-            return offset;
+struct Style {
+    Style(const RenderStyle& style)
+        : font(style.font())
+        , textAlign(style.textAlign())
+        , collapseWhitespace(style.collapseWhiteSpace())
+        , preserveNewline(style.preserveNewline())
+        , wrapLines(style.autoWrap())
+        , breakWordOnOverflow(style.overflowWrap() == BreakOverflowWrap && (wrapLines || preserveNewline))
+        , spaceWidth(font.width(TextRun(&space, 1)))
+        , tabWidth(collapseWhitespace ? 0 : style.tabSize())
+    {
     }
-    return length;
-}
-
-template <typename CharacterType>
-static float textWidth(const RenderText& renderText, const CharacterType* text, unsigned textLength, unsigned from, unsigned to, float xPosition, const RenderStyle& style)
-{
-    if (style.font().isFixedPitch() || (!from && to == textLength))
-        return renderText.width(from, to - from, style.font(), xPosition, nullptr, nullptr);
-    // FIXME: Add templated UChar/LChar paths.
-    TextRun run(text + from, to - from);
-    run.setXPos(xPosition);
-    run.setCharactersLength(textLength - from);
-    ASSERT(run.charactersLength() >= run.length());
-
-    return style.font().width(run);
-}
-
-static float computeLineLeft(ETextAlign textAlign, float remainingWidth)
+    const Font& font;
+    ETextAlign textAlign;
+    bool collapseWhitespace;
+    bool preserveNewline;
+    bool wrapLines;
+    bool breakWordOnOverflow;
+    float spaceWidth;
+    unsigned tabWidth;
+};
+
+static float computeLineLeft(ETextAlign textAlign, float availableWidth, float committedWidth, float logicalLeftOffset)
 {
+    float remainingWidth = availableWidth - committedWidth;
+    float left = logicalLeftOffset;
     switch (textAlign) {
     case LEFT:
     case WEBKIT_LEFT:
     case TASTART:
-        return 0;
+        return left;
     case RIGHT:
     case WEBKIT_RIGHT:
     case TAEND:
-        return std::max<float>(remainingWidth, 0);
+        return left + std::max<float>(remainingWidth, 0);
     case CENTER:
     case WEBKIT_CENTER:
-        return std::max<float>(remainingWidth / 2, 0);
+        return left + std::max<float>(remainingWidth / 2, 0);
     case JUSTIFY:
+        ASSERT_NOT_REACHED();
         break;
     }
     ASSERT_NOT_REACHED();
     return 0;
 }
 
-static void adjustRunOffsets(Vector<Run, 4>& lineRuns, ETextAlign textAlign, float lineWidth, float availableWidth)
-{
-    float lineLeft = computeLineLeft(textAlign, availableWidth - lineWidth);
-    for (unsigned i = 0; i < lineRuns.size(); ++i) {
-        lineRuns[i].left = floor(lineLeft + lineRuns[i].left);
-        lineRuns[i].right = ceil(lineLeft + lineRuns[i].right);
+struct TextFragment {
+    TextFragment()
+        : start(0)
+        , isCollapsedWhitespace(false)
+        , end(0)
+        , isWhitespaceOnly(false)
+        , isBreakable(false)
+        , mustBreak(false)
+        , width(0)
+    {
     }
-}
 
-template <typename CharacterType>
-void createTextRuns(Layout::RunVector& runs, unsigned& lineCount, RenderBlockFlow& flow, RenderText& textRenderer)
-{
-    const RenderStyle& style = flow.style();
+    TextFragment(unsigned textStart, unsigned textEnd, float textWidth, bool isWhitespaceOnly)
+        : start(textStart)
+        , isCollapsedWhitespace(false)
+        , end(textEnd)
+        , isWhitespaceOnly(isWhitespaceOnly)
+        , isBreakable(false)
+        , mustBreak(false)
+        , width(textWidth)
+    {
+    }
 
-    ETextAlign textAlign = style.textAlign();
-    float wordTrailingSpaceWidth = style.font().width(TextRun(&space, 1));
+    bool isEmpty() const
+    {
+        return start == end;
+    }
 
-    const CharacterType* text = textRenderer.text()->getCharacters<CharacterType>();
-    const unsigned textLength = textRenderer.textLength();
+    unsigned start : 31;
+    bool isCollapsedWhitespace : 1;
+    unsigned end : 31;
+    bool isWhitespaceOnly : 1;
+    bool isBreakable;
+    bool mustBreak;
+    float width;
+};
+
+struct LineState {
+    LineState()
+        : availableWidth(0)
+        , logicalLeftOffset(0)
+        , lineStartRunIndex(0)
+        , uncommittedStart(0)
+        , uncommittedEnd(0)
+        , uncommittedWidth(0)
+        , committedWidth(0)
+        , committedLogicalRight(0)
+        , position(0)
+        , trailingWhitespaceWidth(0)
+    {
+    }
 
-    LazyLineBreakIterator lineBreakIterator(textRenderer.text(), style.locale());
+    void commitAndCreateRun(Layout::RunVector& lineRuns)
+    {
+        if (uncommittedStart == uncommittedEnd)
+            return;
 
-    unsigned lineEnd = 0;
-    while (lineEnd < textLength) {
-        lineEnd = skipWhitespaces(text, lineEnd, textLength);
-        unsigned lineStart = lineEnd;
-        unsigned wordEnd = lineEnd;
-        LineWidth lineWidth(flow, false, DoNotIndentText);
+        lineRuns.append(Run(uncommittedStart, uncommittedEnd, committedLogicalRight, committedLogicalRight + uncommittedWidth, false));
+        // Move uncommitted to committed.
+        committedWidth += uncommittedWidth;
+        committedLogicalRight += committedWidth;
 
-        Vector<Run, 4> lineRuns;
-        lineRuns.uncheckedAppend(Run(lineStart, 0));
+        uncommittedStart = uncommittedEnd;
+        uncommittedWidth = 0;
+    }
 
-        while (wordEnd < textLength) {
-            ASSERT(!isWhitespace(text[wordEnd]));
+    void addUncommitted(const TextFragment& fragment)
+    {
+        unsigned uncomittedFragmentLength = fragment.end - uncommittedEnd;
+        uncommittedWidth += fragment.width;
+        uncommittedEnd = fragment.end;
+        position = uncommittedEnd;
+        trailingWhitespaceWidth = fragment.isWhitespaceOnly ? fragment.width : 0;
+        trailingWhitespaceLength = fragment.isWhitespaceOnly ? uncomittedFragmentLength  : 0;
+    }
 
-            bool wordIsPrecededByWhitespace = wordEnd > lineStart && isWhitespace(text[wordEnd - 1]);
-            unsigned wordStart = wordIsPrecededByWhitespace ? wordEnd - 1 : wordEnd;
+    void addUncommittedWhitespace(float whitespaceWidth)
+    {
+        addUncommitted(TextFragment(uncommittedEnd, uncommittedEnd + 1, whitespaceWidth, true));
+    }
 
-            wordEnd = nextBreakablePosition<CharacterType, false>(lineBreakIterator, text, textLength, wordEnd + 1);
+    void jumpTo(unsigned newPositon, float logicalRight)
+    {
+        position = newPositon;
 
-            bool measureWithEndSpace = wordEnd < textLength && text[wordEnd] == ' ';
-            unsigned wordMeasureEnd = measureWithEndSpace ? wordEnd + 1 : wordEnd;
+        uncommittedStart = newPositon;
+        uncommittedEnd = newPositon;
+        uncommittedWidth = 0;
+        committedLogicalRight = logicalRight;
+    }
 
-            float wordWidth = textWidth(textRenderer, text, textLength, wordStart, wordMeasureEnd, lineWidth.committedWidth(), style);
+    float width() const
+    {
+        return committedWidth + uncommittedWidth;
+    }
 
-            if (measureWithEndSpace)
-                wordWidth -= wordTrailingSpaceWidth;
+    bool fits(float extra) const
+    {
+        return availableWidth >= width() + extra;
+    }
 
-            lineWidth.addUncommittedWidth(wordWidth);
+    void removeCommittedTrailingWhitespace()
+    {
+        ASSERT(!uncommittedWidth);
+        committedWidth -= trailingWhitespaceWidth;
+        committedLogicalRight -= trailingWhitespaceWidth;
+    }
 
-            // Move to the next line if the current one is full and we have something on it.
-            if (!lineWidth.fitsOnLine() && lineWidth.committedWidth())
-                break;
+    void resetTrailingWhitespace()
+    {
+        trailingWhitespaceWidth = 0;
+        trailingWhitespaceLength = 0;
+    }
 
-            if (wordStart > lineEnd) {
-                // There were more than one consecutive whitespace.
-                ASSERT(wordIsPrecededByWhitespace);
-                // Include space to the end of the previous run.
-                lineRuns.last().textLength++;
-                lineRuns.last().right += wordTrailingSpaceWidth;
-                // Start a new run on the same line.
-                lineRuns.append(Run(wordStart + 1, lineRuns.last().right));
-            }
+    float availableWidth;
+    float logicalLeftOffset;
+    unsigned lineStartRunIndex; // The run that the line starts with.
 
-            lineWidth.commit();
+    unsigned uncommittedStart;
+    unsigned uncommittedEnd;
+    float uncommittedWidth;
+    float committedWidth;
+    float committedLogicalRight; // Last committed X (coordinate) position.
 
-            lineRuns.last().right = lineWidth.committedWidth();
-            lineRuns.last().textLength = wordEnd - lineRuns.last().textOffset;
+    unsigned position;
 
-            lineEnd = wordEnd;
-            wordEnd = skipWhitespaces(text, wordEnd, textLength);
+    float trailingWhitespaceWidth; // Use this to remove trailing whitespace without re-mesuring the text.
+    float trailingWhitespaceLength;
 
-            if (!lineWidth.fitsOnLine()) {
-                // The first run on the line overflows.
-                ASSERT(lineRuns.size() == 1);
-                break;
-            }
-        }
-        if (lineStart == lineEnd)
-            continue;
+    TextFragment oveflowedFragment;
+};
 
-        adjustRunOffsets(lineRuns, textAlign, lineWidth.committedWidth(), lineWidth.availableWidth());
+static void removeTrailingWhitespace(LineState& lineState, Layout::RunVector& lineRuns, const FlowContents& flowContents)
+{
+    const auto& style = flowContents.style();
+    bool preWrap = style.wrapLines && !style.collapseWhitespace;
+    // Trailing whitespace gets removed when we either collapse whitespace or pre-wrap is present.
+    if (!(style.collapseWhitespace || preWrap)) {
+        lineState.resetTrailingWhitespace();
+        return;
+    }
 
-        for (unsigned i = 0; i < lineRuns.size(); ++i)
-            runs.append(lineRuns[i]);
+    ASSERT(lineRuns.size());
+    Run& lastRun = lineRuns.last();
+
+    unsigned lastPosition = lineState.position;
+    bool trailingPreWrapWhitespaceNeedsToBeRemoved = false;
+    // When pre-wrap is present, trailing whitespace needs to be removed:
+    // 1. from the "next line": when at least the first charater fits. When even the first whitespace is wider that the available width,
+    // we don't remove any whitespace at all.
+    // 2. from this line: remove whitespace, unless it's the only fragment on the line -so removing the whitesapce would produce an empty line.
+    if (preWrap) {
+        if (lineState.oveflowedFragment.isWhitespaceOnly && !lineState.oveflowedFragment.isEmpty() && lineState.availableWidth >= lineState.committedWidth) {
+            lineState.position = lineState.oveflowedFragment.end;
+            lineState.oveflowedFragment = TextFragment();
+        }
+        if (lineState.trailingWhitespaceLength) {
+            // Check if we've got only whitespace on this line.
+            trailingPreWrapWhitespaceNeedsToBeRemoved = !(lineState.committedWidth == lineState.trailingWhitespaceWidth);
+        }
+    }
+    if (lineState.trailingWhitespaceLength && (style.collapseWhitespace || trailingPreWrapWhitespaceNeedsToBeRemoved)) {
+        lastRun.logicalRight -= lineState.trailingWhitespaceWidth;
+        lastRun.end -= lineState.trailingWhitespaceLength;
+        if (lastRun.start == lastRun.end)
+            lineRuns.removeLast();
+        lineState.removeCommittedTrailingWhitespace();
+    }
 
-        runs.last().isEndOfLine = true;
-        ++lineCount;
+    // If we skipped any whitespace and now the line end is a "preserved" newline, skip the newline too as we are wrapping the line here already.
+    if (lastPosition != lineState.position && style.preserveNewline && !flowContents.isEndOfContent(lineState.position) && flowContents.isNewlineCharacter(lineState.position))
+        ++lineState.position;
+}
+
+static void initializeNewLine(LineState& lineState, const FlowContents& flowContents, unsigned lineStartRunIndex)
+{
+    lineState.lineStartRunIndex = lineStartRunIndex;
+    // Skip leading whitespace if collapsing whitespace, unless there's an uncommitted fragment pushed from the previous line.
+    // FIXME: Be smarter when the run from the previous line does not fit the current line. Right now, we just reprocess it.
+    if (lineState.oveflowedFragment.width) {
+        if (lineState.fits(lineState.oveflowedFragment.width))
+            lineState.addUncommitted(lineState.oveflowedFragment);
+        else
+            lineState.jumpTo(lineState.oveflowedFragment.start, 0); // Start over with this fragment.
+    } else {
+        unsigned spaceCount = 0;
+        lineState.jumpTo(flowContents.style().collapseWhitespace ? flowContents.findNextNonWhitespacePosition(lineState.position, spaceCount) : lineState.position, 0);
     }
+    lineState.oveflowedFragment = TextFragment();
 }
 
-std::unique_ptr<Layout> create(RenderBlockFlow& flow)
+static TextFragment splitFragmentToFitLine(TextFragment& fragmentToSplit, float availableWidth, bool keepAtLeastOneCharacter, const FlowContents& flowContents)
 {
-    Layout::RunVector runs;
-    unsigned lineCount = 0;
+    // Fast path for single char fragments.
+    if (fragmentToSplit.start + 1 == fragmentToSplit.end) {
+        if (keepAtLeastOneCharacter)
+            return TextFragment();
+
+        TextFragment fragmentForNextLine(fragmentToSplit);
+        fragmentToSplit.end = fragmentToSplit.start;
+        fragmentToSplit.width = 0;
+        return fragmentForNextLine;
+    }
+    // Simple binary search to find out what fits the current line.
+    // FIXME: add surrogate pair support.
+    unsigned left = fragmentToSplit.start;
+    unsigned right = fragmentToSplit.end - 1; // We can ignore the last character. It surely does not fit.
+    float width = 0;
+    while (left < right) {
+        unsigned middle = (left + right) / 2;
+        width = flowContents.textWidth(fragmentToSplit.start, middle + 1, 0);
+        if (availableWidth > width)
+            left = middle + 1;
+        else if (availableWidth < width)
+            right = middle;
+        else {
+            right = middle + 1;
+            break;
+        }
+    }
 
-    RenderText& textRenderer = toRenderText(*flow.firstChild());
-    ASSERT(!textRenderer.firstTextBox());
+    if (keepAtLeastOneCharacter && right == fragmentToSplit.start)
+        ++right;
+    TextFragment fragmentForNextLine(fragmentToSplit);
+    fragmentToSplit.end = right;
+    fragmentToSplit.width = fragmentToSplit.isEmpty() ? 0 : flowContents.textWidth(fragmentToSplit.start, right, 0);
 
-    if (textRenderer.is8Bit())
-        createTextRuns<LChar>(runs, lineCount, flow, textRenderer);
+    fragmentForNextLine.start = fragmentToSplit.end;
+    fragmentForNextLine.width -= fragmentToSplit.width;
+    return fragmentForNextLine;
+}
+
+static TextFragment nextFragment(unsigned previousFragmentEnd, const FlowContents& flowContents, float xPosition)
+{
+    // A fragment can have
+    // 1. new line character when preserveNewline is on (not considered as whitespace) or
+    // 2. whitespace (collasped, non-collapsed multi or single) or
+    // 3. non-whitespace characters.
+    const auto& style = flowContents.style();
+    TextFragment fragment;
+    fragment.mustBreak = style.preserveNewline && flowContents.isNewlineCharacter(previousFragmentEnd);
+    unsigned spaceCount = 0;
+    unsigned whitespaceEnd = previousFragmentEnd;
+    if (!fragment.mustBreak)
+        whitespaceEnd = flowContents.findNextNonWhitespacePosition(previousFragmentEnd, spaceCount);
+    fragment.isWhitespaceOnly = previousFragmentEnd < whitespaceEnd;
+    fragment.start = previousFragmentEnd;
+    if (fragment.isWhitespaceOnly)
+        fragment.end = whitespaceEnd;
+    else if (fragment.mustBreak)
+        fragment.end = fragment.start + 1;
     else
-        createTextRuns<UChar>(runs, lineCount, flow, textRenderer);
+        fragment.end = flowContents.findNextBreakablePosition(previousFragmentEnd + 1);
+    bool multiple = fragment.start + 1 < fragment.end;
+    fragment.isCollapsedWhitespace = multiple && fragment.isWhitespaceOnly && style.collapseWhitespace;
+    // Non-collapsed whitespace or just plain words when "break word on overflow" is on can wrap.
+    fragment.isBreakable = multiple && ((fragment.isWhitespaceOnly && !fragment.isCollapsedWhitespace) || (!fragment.isWhitespaceOnly && style.breakWordOnOverflow));
+
+    // Compute fragment width or just use the pre-computed whitespace widths.
+    unsigned fragmentLength = fragment.end - fragment.start;
+    if (fragment.isCollapsedWhitespace)
+        fragment.width = style.spaceWidth;
+    else if (fragment.mustBreak)
+        fragment.width = 0; // Newline character's width is 0.
+    else if (fragmentLength == spaceCount) // Space only.
+        fragment.width = style.spaceWidth * spaceCount;
+    else
+        fragment.width = flowContents.textWidth(fragment.start, fragment.end, xPosition);
+    return fragment;
+}
 
-    textRenderer.clearNeedsLayout();
+static bool createLineRuns(LineState& lineState, Layout::RunVector& lineRuns, const FlowContents& flowContents)
+{
+    const auto& style = flowContents.style();
+    bool lineCanBeWrapped = style.wrapLines || style.breakWordOnOverflow;
+    while (!flowContents.isEndOfContent(lineState.position)) {
+        // Find the next text fragment. Start from the end of the previous fragment -current line end.
+        TextFragment fragment = nextFragment(lineState.position, flowContents, lineState.width());
+        if ((lineCanBeWrapped && !lineState.fits(fragment.width)) || fragment.mustBreak) {
+            // Overflow wrapping behaviour:
+            // 1. Newline character: wraps the line unless it's treated as whitespace.
+            // 2. Whitesapce collapse on: whitespace is skipped.
+            // 3. Whitespace collapse off: whitespace is wrapped.
+            // 4. First, non-whitespace fragment is either wrapped or kept on the line. (depends on overflow-wrap)
+            // 5. Non-whitespace fragment when there's already another fragment on the line gets pushed to the next line.
+            bool isFirstFragment = !lineState.width();
+            if (fragment.mustBreak) {
+                if (isFirstFragment)
+                    lineState.addUncommitted(fragment);
+                else {
+                    // No need to add the new line fragment if there's already content on the line. We are about to close this line anyway.
+                    ++lineState.position;
+                }
+            } else if (style.collapseWhitespace && fragment.isWhitespaceOnly) {
+                // Whitespace collapse is on: whitespace that doesn't fit is simply skipped.
+                lineState.position = fragment.end;
+            } else if (fragment.isWhitespaceOnly || ((isFirstFragment && style.breakWordOnOverflow) || !style.wrapLines)) { // !style.wrapLines: bug138102(preserve existing behavior)
+                // Whitespace collapse is off or non-whitespace content. split the fragment; (modified)fragment -> this lineState, oveflowedFragment -> next line.
+                // When this is the only (first) fragment, the first character stays on the line, even if it does not fit.
+                lineState.oveflowedFragment = splitFragmentToFitLine(fragment, lineState.availableWidth - lineState.width(), isFirstFragment, flowContents);
+                if (!fragment.isEmpty()) {
+                    // Whitespace fragments can get pushed entirely to the next line.
+                    lineState.addUncommitted(fragment);
+                }
+            } else if (isFirstFragment) {
+                // Non-breakable non-whitespace first fragment. Add it to the current line. -it overflows though.
+                lineState.addUncommitted(fragment);
+            } else {
+                // Non-breakable non-whitespace fragment when there's already a fragment on the line. Push it to the next line.
+                lineState.oveflowedFragment = fragment;
+            }
+            break;
+        }
+        // When the current fragment is collapsed whitespace, we need to create a run for what we've processed so far.
+        if (fragment.isCollapsedWhitespace) {
+            // One trailing whitespace to preserve.
+            lineState.addUncommittedWhitespace(style.spaceWidth);
+            lineState.commitAndCreateRun(lineRuns);
+            // And skip the collapsed whitespace.
+            lineState.jumpTo(fragment.end, lineState.width() + fragment.width - style.spaceWidth);
+        } else
+            lineState.addUncommitted(fragment);
+    }
+    lineState.commitAndCreateRun(lineRuns);
+    return flowContents.isEndOfContent(lineState.position) && lineState.oveflowedFragment.isEmpty();
+}
+
+static void closeLineEndingAndAdjustRuns(LineState& lineState, Layout::RunVector& lineRuns, unsigned& lineCount, const FlowContents& flowContents)
+{
+    if (lineState.lineStartRunIndex == lineRuns.size())
+        return;
+
+    ASSERT(lineRuns.size());
+    removeTrailingWhitespace(lineState, lineRuns, flowContents);
+    // Adjust runs' position by taking line's alignment into account.
+    if (float lineLogicalLeft = computeLineLeft(flowContents.style().textAlign, lineState.availableWidth, lineState.committedWidth, lineState.logicalLeftOffset)) {
+        for (unsigned i = lineState.lineStartRunIndex; i < lineRuns.size(); ++i) {
+            lineRuns[i].logicalLeft += lineLogicalLeft;
+            lineRuns[i].logicalRight += lineLogicalLeft;
+        }
+    }
+    lineRuns.last().isEndOfLine = true;
+    lineState.committedWidth = 0;
+    lineState.committedLogicalRight = 0;
+    ++lineCount;
+}
+
+static void splitRunsAtRendererBoundary(Layout::RunVector& lineRuns, const FlowContents& flowContents)
+{
+    if (!lineRuns.size())
+        return;
+
+    unsigned runIndex = 0;
+    do {
+        const Run& run = lineRuns.at(runIndex);
+        ASSERT(run.start != run.end);
+        const RenderText* startRenderer = flowContents.renderer(run.start);
+        const RenderText* endRenderer = flowContents.renderer(run.end - 1);
+        if (startRenderer == endRenderer)
+            continue;
+        // This run overlaps multiple renderers. Split it up.
+        unsigned rendererStartPosition = 0;
+        unsigned rendererEndPosition = 0;
+        bool found = flowContents.resolveRendererPositions(*startRenderer, rendererStartPosition, rendererEndPosition);
+        ASSERT_UNUSED(found, found);
+
+        // 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 + flowContents.textWidth(run.start, rendererEndPosition, run.logicalLeft);
+        lineRuns.insert(runIndex, Run(run.start, rendererEndPosition, run.logicalLeft, logicalRightOfLeftRun, false));
+        Run& rightSideRun = lineRuns.at(runIndex + 1);
+        rightSideRun.start = rendererEndPosition;
+        rightSideRun.logicalLeft = logicalRightOfLeftRun;
+    } while (++runIndex < lineRuns.size());
+}
+
+static void updateLineConstrains(const RenderBlockFlow& flow, float& availableWidth, float& logicalLeftOffset)
+{
+    LayoutUnit height = flow.logicalHeight();
+    LayoutUnit logicalHeight = flow.minLineHeightForReplacedRenderer(false, 0);
+    float logicalRightOffset = flow.logicalRightOffsetForLine(height, false, logicalHeight);
+    logicalLeftOffset = flow.logicalLeftOffsetForLine(height, false, logicalHeight);
+    availableWidth = std::max<float>(0, logicalRightOffset - logicalLeftOffset);
+}
+
+static void createTextRuns(Layout::RunVector& runs, RenderBlockFlow& flow, unsigned& lineCount)
+{
+    LayoutUnit borderAndPaddingBefore = flow.borderAndPaddingBefore();
+    LayoutUnit lineHeight = lineHeightFromFlow(flow);
+    LineState lineState;
+    bool isEndOfContent = false;
+    FlowContents flowContents = FlowContents(flow);
+
+    do {
+        flow.setLogicalHeight(lineHeight * lineCount + borderAndPaddingBefore);
+        updateLineConstrains(flow, lineState.availableWidth, lineState.logicalLeftOffset);
+        initializeNewLine(lineState, flowContents, runs.size());
+        isEndOfContent = createLineRuns(lineState, runs, flowContents);
+        closeLineEndingAndAdjustRuns(lineState, runs, lineCount, flowContents);
+    } while (!isEndOfContent);
+
+    if (flow.firstChild() != flow.lastChild())
+        splitRunsAtRendererBoundary(runs, flowContents);
+    ASSERT(!lineState.uncommittedWidth);
+}
 
+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);
 }