Simple line layout: Introduce text fragment continuation.
[WebKit-https.git] / Source / WebCore / rendering / SimpleLineLayout.cpp
index 6106c1d..f2c3345 100644 (file)
 #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 "SimpleLineLayoutFlowContents.h"
 #include "SimpleLineLayoutFunctions.h"
 #include "Text.h"
 #include "TextPaintStyle.h"
-#include "break_lines.h"
 
 namespace WebCore {
 namespace SimpleLineLayout {
@@ -93,12 +94,12 @@ bool canUseFor(const RenderBlockFlow& flow)
         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;
+    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)
@@ -116,7 +117,7 @@ bool canUseFor(const RenderBlockFlow& flow)
     if (flow.parent()->isTextArea() && flow.parent()->element()->fastHasAttribute(HTMLNames::wrapAttr))
         return false;
     // FIXME: Placeholders do something strange.
-    if (flow.parent()->isTextControl() && toRenderTextControl(*flow.parent()).textFormControlElement().placeholderElement())
+    if (is<RenderTextControl>(*flow.parent()) && downcast<RenderTextControl>(*flow.parent()).textFormControlElement().placeholderElement())
         return false;
     const RenderStyle& style = flow.style();
     if (style.textDecorationsInEffect() != TextDecorationNone)
@@ -130,8 +131,6 @@ bool canUseFor(const RenderBlockFlow& flow)
         return false;
     if (!style.wordSpacing().isZero() || style.letterSpacing())
         return false;
-    if (style.textTransform() != TTNONE)
-        return false;
     if (!style.isLeftToRightDirection())
         return false;
     if (style.lineBoxContain() != RenderStyle::initialLineBoxContain())
@@ -162,7 +161,7 @@ bool canUseFor(const RenderBlockFlow& flow)
         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();
@@ -213,244 +212,445 @@ struct Style {
     unsigned tabWidth;
 };
 
-static inline bool isWhitespace(UChar character, bool preserveNewline)
+static float computeLineLeft(ETextAlign textAlign, float availableWidth, float committedWidth, float logicalLeftOffset)
 {
-    return character == ' ' || character == '\t' || (!preserveNewline && character == '\n');
+    float remainingWidth = availableWidth - committedWidth;
+    float left = logicalLeftOffset;
+    switch (textAlign) {
+    case LEFT:
+    case WEBKIT_LEFT:
+    case TASTART:
+        return left;
+    case RIGHT:
+    case WEBKIT_RIGHT:
+    case TAEND:
+        return left + std::max<float>(remainingWidth, 0);
+    case CENTER:
+    case WEBKIT_CENTER:
+        return left + std::max<float>(remainingWidth / 2, 0);
+    case JUSTIFY:
+        ASSERT_NOT_REACHED();
+        break;
+    }
+    ASSERT_NOT_REACHED();
+    return 0;
 }
 
-template <typename CharacterType>
-static inline unsigned skipWhitespaces(const CharacterType* text, unsigned offset, unsigned length, bool preserveNewline)
-{
-    for (; offset < length; ++offset) {
-        if (!isWhitespace(text[offset], preserveNewline))
-            return offset;
+struct TextFragment {
+    TextFragment()
+        : start(0)
+        , isCollapsedWhitespace(false)
+        , end(0)
+        , isWhitespaceOnly(false)
+        , isBreakable(false)
+        , mustBreak(false)
+        , width(0)
+    {
     }
-    return length;
-}
 
-template <typename CharacterType>
-static float textWidth(const RenderText& renderText, const CharacterType* text, unsigned textLength, unsigned from, unsigned to, float xPosition, const Style& style)
-{
-    if (style.font.isFixedPitch() || (!from && to == textLength))
-        return renderText.width(from, to - from, style.font, xPosition, nullptr, nullptr);
+    TextFragment(unsigned textStart, unsigned textEnd, float textWidth, bool isWhitespaceOnly)
+        : start(textStart)
+        , isCollapsedWhitespace(false)
+        , end(textEnd)
+        , isWhitespaceOnly(isWhitespaceOnly)
+        , isBreakable(false)
+        , mustBreak(false)
+        , width(textWidth)
+    {
+    }
 
-    TextRun run(text + from, to - from);
-    run.setXPos(xPosition);
-    run.setCharactersLength(textLength - from);
-    run.setTabSize(!!style.tabWidth, style.tabWidth);
+    bool isEmpty() const
+    {
+        return start == end;
+    }
 
-    ASSERT(run.charactersLength() >= run.length());
+    unsigned start : 31;
+    bool isCollapsedWhitespace : 1;
+    unsigned end : 31;
+    bool isWhitespaceOnly : 1;
+    bool isBreakable;
+    bool mustBreak;
+    float width;
+};
 
-    return style.font.width(run);
-}
+struct LineState {
+    LineState()
+        : availableWidth(0)
+        , logicalLeftOffset(0)
+        , lineStartRunIndex(0)
+        , uncommittedStart(0)
+        , uncommittedEnd(0)
+        , uncommittedWidth(0)
+        , committedWidth(0)
+        , committedLogicalRight(0)
+        , position(0)
+        , trailingWhitespaceWidth(0)
+    {
+    }
 
-template <typename CharacterType>
-static float measureWord(unsigned start, unsigned end, float lineWidth, const Style& style, const CharacterType* text, unsigned textLength, const RenderText& textRenderer)
-{
-    if (text[start] == ' ' && end == start + 1)
-        return style.spaceWidth;
+    void commitAndCreateRun(Layout::RunVector& lineRuns)
+    {
+        if (uncommittedStart == uncommittedEnd)
+            return;
 
-    bool measureWithEndSpace = style.collapseWhitespace && end < textLength && text[end] == ' ';
-    if (measureWithEndSpace)
-        ++end;
-    float width = textWidth(textRenderer, text, textLength, start, end, lineWidth, style);
+        lineRuns.append(Run(uncommittedStart, uncommittedEnd, committedLogicalRight, committedLogicalRight + uncommittedWidth, false));
+        // Move uncommitted to committed.
+        committedWidth += uncommittedWidth;
+        committedLogicalRight += committedWidth;
 
-    return measureWithEndSpace ? width - style.spaceWidth : width;
-}
+        uncommittedStart = uncommittedEnd;
+        uncommittedWidth = 0;
+    }
 
-template <typename CharacterType>
-Vector<Run, 4> createLineRuns(unsigned lineStart, LineWidth& lineWidth, LazyLineBreakIterator& lineBreakIterator, const Style& style, const CharacterType* text, unsigned textLength, const RenderText& textRenderer)
-{
-    Vector<Run, 4> lineRuns;
-    lineRuns.uncheckedAppend(Run(lineStart, 0));
-
-    unsigned wordEnd = lineStart;
-    while (wordEnd < textLength) {
-        ASSERT(!style.collapseWhitespace || !isWhitespace(text[wordEnd], style.preserveNewline));
-
-        unsigned wordStart = wordEnd;
-
-        if (style.preserveNewline && text[wordStart] == '\n') {
-            ++wordEnd;
-            // FIXME: This creates a dedicated run for newline. This is wasteful and unnecessary but it keeps test results unchanged.
-            if (wordStart > lineStart)
-                lineRuns.append(Run(wordStart, lineRuns.last().right));
-            lineRuns.last().right = lineRuns.last().left;
-            lineRuns.last().end = wordEnd;
-            break;
-        }
+    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;
+    }
 
-        if (!style.collapseWhitespace && isWhitespace(text[wordStart], style.preserveNewline))
-            wordEnd = wordStart + 1;
-        else
-            wordEnd = nextBreakablePosition<CharacterType, false>(lineBreakIterator, text, textLength, wordStart + 1);
-
-        bool wordIsPrecededByWhitespace = style.collapseWhitespace && wordStart > lineStart && isWhitespace(text[wordStart - 1], style.preserveNewline);
-        if (wordIsPrecededByWhitespace)
-            --wordStart;
-
-        float wordWidth = measureWord(wordStart, wordEnd, lineWidth.committedWidth(), style, text, textLength, textRenderer);
-
-        lineWidth.addUncommittedWidth(wordWidth);
-
-        if (style.wrapLines) {
-            // Move to the next line if the current one is full and we have something on it.
-            if (!lineWidth.fitsOnLine() && lineWidth.committedWidth())
-                break;
-
-            // This is for white-space: pre-wrap which requires special handling for end line whitespace.
-            if (!style.collapseWhitespace && lineWidth.fitsOnLine() && wordEnd < textLength && isWhitespace(text[wordEnd], style.preserveNewline)) {
-                // Look ahead to see if the next whitespace would fit.
-                float whitespaceWidth = textWidth(textRenderer, text, textLength, wordEnd, wordEnd + 1, lineWidth.committedWidth(), style);
-                if (!lineWidth.fitsOnLineIncludingExtraWidth(whitespaceWidth)) {
-                    // If not eat away the rest of the whitespace on the line.
-                    unsigned whitespaceEnd = skipWhitespaces(text, wordEnd, textLength, style.preserveNewline);
-                    // Include newline to this run too.
-                    if (whitespaceEnd < textLength && text[whitespaceEnd] == '\n')
-                        ++whitespaceEnd;
-                    lineRuns.last().end = whitespaceEnd;
-                    lineRuns.last().right = lineWidth.availableWidth();
-                    break;
-                }
-            }
-        }
+    void addUncommittedWhitespace(float whitespaceWidth)
+    {
+        addUncommitted(TextFragment(uncommittedEnd, uncommittedEnd + 1, whitespaceWidth, true));
+    }
 
-        if (wordStart > lineRuns.last().end) {
-            // There were more than one consecutive whitespace.
-            ASSERT(wordIsPrecededByWhitespace);
-            // Include space to the end of the previous run.
-            lineRuns.last().end++;
-            lineRuns.last().right += style.spaceWidth;
-            // Start a new run on the same line.
-            lineRuns.append(Run(wordStart + 1, lineRuns.last().right));
-        }
+    void jumpTo(unsigned newPositon, float logicalRight)
+    {
+        position = newPositon;
 
-        if (!lineWidth.fitsOnLine() && style.breakWordOnOverflow) {
-            // Backtrack and start measuring character-by-character.
-            lineWidth.addUncommittedWidth(-lineWidth.uncommittedWidth());
-            unsigned splitEnd = wordStart;
-            for (; splitEnd < wordEnd; ++splitEnd) {
-                float charWidth = textWidth(textRenderer, text, textLength, splitEnd, splitEnd + 1, 0, style);
-                lineWidth.addUncommittedWidth(charWidth);
-                if (!lineWidth.fitsOnLine() && splitEnd > lineStart)
-                    break;
-                lineWidth.commit();
-            }
-            lineRuns.last().end = splitEnd;
-            lineRuns.last().right = lineWidth.committedWidth();
-            // To match line boxes, set single-space-only line width to zero.
-            if (text[lineRuns.last().start] == ' ' && lineRuns.last().start + 1 == lineRuns.last().end)
-                lineRuns.last().right = lineRuns.last().left;
-            break;
-        }
+        uncommittedStart = newPositon;
+        uncommittedEnd = newPositon;
+        uncommittedWidth = 0;
+        committedLogicalRight = logicalRight;
+    }
 
-        lineWidth.commit();
+    float width() const
+    {
+        return committedWidth + uncommittedWidth;
+    }
 
-        lineRuns.last().right = lineWidth.committedWidth();
-        lineRuns.last().end = wordEnd;
+    bool fits(float extra) const
+    {
+        return availableWidth >= width() + extra;
+    }
 
-        if (style.collapseWhitespace)
-            wordEnd = skipWhitespaces(text, wordEnd, textLength, style.preserveNewline);
+    void removeCommittedTrailingWhitespace()
+    {
+        ASSERT(!uncommittedWidth);
+        committedWidth -= trailingWhitespaceWidth;
+        committedLogicalRight -= trailingWhitespaceWidth;
+    }
 
-        if (!lineWidth.fitsOnLine() && style.wrapLines) {
-            // The first run on the line overflows.
-            ASSERT(lineRuns.size() == 1);
-            break;
-        }
+    void resetTrailingWhitespace()
+    {
+        trailingWhitespaceWidth = 0;
+        trailingWhitespaceLength = 0;
     }
-    return lineRuns;
-}
 
-static float computeLineLeft(ETextAlign textAlign, const LineWidth& lineWidth)
+    float availableWidth;
+    float logicalLeftOffset;
+    unsigned lineStartRunIndex; // The run that the line starts with.
+
+    unsigned uncommittedStart;
+    unsigned uncommittedEnd;
+    float uncommittedWidth;
+    float committedWidth;
+    float committedLogicalRight; // Last committed X (coordinate) position.
+
+    unsigned position;
+
+    float trailingWhitespaceWidth; // Use this to remove trailing whitespace without re-mesuring the text.
+    float trailingWhitespaceLength;
+
+    TextFragment oveflowedFragment;
+};
+
+static void removeTrailingWhitespace(LineState& lineState, Layout::RunVector& lineRuns, const FlowContents& flowContents)
 {
-    float remainingWidth = lineWidth.availableWidth() - lineWidth.committedWidth();
-    float left = lineWidth.logicalLeftOffset();
-    switch (textAlign) {
-    case LEFT:
-    case WEBKIT_LEFT:
-    case TASTART:
-        return left;
-    case RIGHT:
-    case WEBKIT_RIGHT:
-    case TAEND:
-        return left + std::max<float>(remainingWidth, 0);
-    case CENTER:
-    case WEBKIT_CENTER:
-        return left + std::max<float>(remainingWidth / 2, 0);
-    case JUSTIFY:
-        break;
+    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;
     }
-    ASSERT_NOT_REACHED();
-    return 0;
+
+    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();
+    }
+
+    // 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 adjustRunOffsets(Vector<Run, 4>& lineRuns, float adjustment)
+static void initializeNewLine(LineState& lineState, const FlowContents& flowContents, unsigned lineStartRunIndex)
 {
-    if (!adjustment)
-        return;
-    for (unsigned i = 0; i < lineRuns.size(); ++i) {
-        lineRuns[i].left += adjustment;
-        lineRuns[i].right += adjustment;
+    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();
 }
 
-template <typename CharacterType>
-void createTextRuns(Layout::RunVector& runs, unsigned& lineCount, RenderBlockFlow& flow, RenderText& textRenderer)
+static TextFragment splitFragmentToFitLine(TextFragment& fragmentToSplit, float availableWidth, bool keepAtLeastOneCharacter, const FlowContents& flowContents)
 {
-    const Style style(flow.style());
+    // 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;
+        }
+    }
 
-    const CharacterType* text = textRenderer.text()->characters<CharacterType>();
-    const unsigned textLength = textRenderer.textLength();
+    if (keepAtLeastOneCharacter && right == fragmentToSplit.start)
+        ++right;
+    TextFragment fragmentForNextLine(fragmentToSplit);
+    fragmentToSplit.end = right;
+    fragmentToSplit.width = fragmentToSplit.isEmpty() ? 0 : flowContents.textWidth(fragmentToSplit.start, right, 0);
 
-    LayoutUnit borderAndPaddingBefore = flow.borderAndPaddingBefore();
-    LayoutUnit lineHeight = lineHeightFromFlow(flow);
+    fragmentForNextLine.start = fragmentToSplit.end;
+    fragmentForNextLine.width -= fragmentToSplit.width;
+    return fragmentForNextLine;
+}
 
-    LazyLineBreakIterator lineBreakIterator(textRenderer.text(), flow.style().locale());
+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
+        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;
+}
 
-    unsigned lineEnd = 0;
-    while (lineEnd < textLength) {
-        if (style.collapseWhitespace)
-            lineEnd = skipWhitespaces(text, lineEnd, textLength, style.preserveNewline);
+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();
+}
 
-        unsigned lineStart = lineEnd;
+static void closeLineEndingAndAdjustRuns(LineState& lineState, Layout::RunVector& lineRuns, unsigned& lineCount, const FlowContents& flowContents)
+{
+    if (lineState.lineStartRunIndex == lineRuns.size())
+        return;
 
-        // LineWidth reads the current y position from the flow so keep it updated.
-        flow.setLogicalHeight(lineHeight * lineCount + borderAndPaddingBefore);
-        LineWidth lineWidth(flow, false, DoNotIndentText);
+    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;
+}
 
-        auto lineRuns = createLineRuns(lineStart, lineWidth, lineBreakIterator, style, text, textLength, textRenderer);
+static void splitRunsAtRendererBoundary(Layout::RunVector& lineRuns, const FlowContents& flowContents)
+{
+    if (!lineRuns.size())
+        return;
 
-        lineEnd = lineRuns.last().end;
-        if (lineStart == lineEnd)
+    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());
+}
 
-        lineRuns.last().isEndOfLine = true;
+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);
+}
 
-        float lineLeft = computeLineLeft(style.textAlign, lineWidth);
-        adjustRunOffsets(lineRuns, lineLeft);
+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);
 
-        for (unsigned i = 0; i < lineRuns.size(); ++i)
-            runs.append(lineRuns[i]);
+    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);
 
-        ++lineCount;
-    }
+    if (flow.firstChild() != flow.lastChild())
+        splitRunsAtRendererBoundary(runs, flowContents);
+    ASSERT(!lineState.uncommittedWidth);
 }
 
 std::unique_ptr<Layout> create(RenderBlockFlow& flow)
 {
-    Layout::RunVector runs;
     unsigned lineCount = 0;
+    Layout::RunVector runs;
 
-    RenderText& textRenderer = toRenderText(*flow.firstChild());
-    ASSERT(!textRenderer.firstTextBox());
-
-    if (textRenderer.is8Bit())
-        createTextRuns<LChar>(runs, lineCount, flow, textRenderer);
-    else
-        createTextRuns<UChar>(runs, lineCount, flow, textRenderer);
-
-    textRenderer.clearNeedsLayout();
-
+    createTextRuns(runs, flow, lineCount);
+    for (auto& renderer : childrenOfType<RenderObject>(flow)) {
+        ASSERT(is<RenderText>(renderer));
+        renderer.clearNeedsLayout();
+    }
     return Layout::create(runs, lineCount);
 }