Simple line layout: Abstract out content iteration and text handling in general.
authorzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 5 Nov 2014 03:47:45 +0000 (03:47 +0000)
committerzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 5 Nov 2014 03:47:45 +0000 (03:47 +0000)
https://bugs.webkit.org/show_bug.cgi?id=138346

Reviewed by Antti Koivisto.

Add a class that can act as the primary iterator/fragment handling interface to the line parser.
This helps adding support multiple renderer elements without changing the parser logic.
Currently it operates strictly on the first child of the RenderBlockFlow.

Covered by existing tests.

* rendering/SimpleLineLayout.cpp:
(WebCore::SimpleLineLayout::FlowContentIterator::FlowContentIterator):
(WebCore::SimpleLineLayout::FlowContentIterator::findNextBreakablePosition):
(WebCore::SimpleLineLayout::FlowContentIterator::findNextNonWhitespacePosition):
(WebCore::SimpleLineLayout::FlowContentIterator::textWidth):
(WebCore::SimpleLineLayout::FlowContentIterator::isNewlineCharacter):
(WebCore::SimpleLineLayout::FlowContentIterator::isEndOfContent):
(WebCore::SimpleLineLayout::FlowContentIterator::style):
(WebCore::SimpleLineLayout::computeLineLeft):
(WebCore::SimpleLineLayout::TextFragment::TextFragment):
(WebCore::SimpleLineLayout::removeTrailingWhitespace):
(WebCore::SimpleLineLayout::initializeNewLine):
(WebCore::SimpleLineLayout::splitFragmentToFitLine):
(WebCore::SimpleLineLayout::nextFragment):
(WebCore::SimpleLineLayout::createLineRuns):
(WebCore::SimpleLineLayout::closeLineEndingAndAdjustRuns):
(WebCore::SimpleLineLayout::createTextRuns):
(WebCore::SimpleLineLayout::create):
(WebCore::SimpleLineLayout::skipWhitespace): Deleted.
(WebCore::SimpleLineLayout::textWidth): Deleted.
* rendering/SimpleLineLayout.h:

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

Source/WebCore/ChangeLog
Source/WebCore/rendering/SimpleLineLayout.cpp
Source/WebCore/rendering/SimpleLineLayout.h

index 99b507d..f3762ca 100644 (file)
@@ -1,3 +1,38 @@
+2014-11-04  Zalan Bujtas  <zalan@apple.com>
+
+        Simple line layout: Abstract out content iteration and text handling in general.
+        https://bugs.webkit.org/show_bug.cgi?id=138346
+
+        Reviewed by Antti Koivisto.
+
+        Add a class that can act as the primary iterator/fragment handling interface to the line parser.
+        This helps adding support multiple renderer elements without changing the parser logic.
+        Currently it operates strictly on the first child of the RenderBlockFlow.  
+
+        Covered by existing tests.
+
+        * rendering/SimpleLineLayout.cpp:
+        (WebCore::SimpleLineLayout::FlowContentIterator::FlowContentIterator):
+        (WebCore::SimpleLineLayout::FlowContentIterator::findNextBreakablePosition):
+        (WebCore::SimpleLineLayout::FlowContentIterator::findNextNonWhitespacePosition):
+        (WebCore::SimpleLineLayout::FlowContentIterator::textWidth):
+        (WebCore::SimpleLineLayout::FlowContentIterator::isNewlineCharacter):
+        (WebCore::SimpleLineLayout::FlowContentIterator::isEndOfContent):
+        (WebCore::SimpleLineLayout::FlowContentIterator::style):
+        (WebCore::SimpleLineLayout::computeLineLeft):
+        (WebCore::SimpleLineLayout::TextFragment::TextFragment):
+        (WebCore::SimpleLineLayout::removeTrailingWhitespace):
+        (WebCore::SimpleLineLayout::initializeNewLine):
+        (WebCore::SimpleLineLayout::splitFragmentToFitLine):
+        (WebCore::SimpleLineLayout::nextFragment):
+        (WebCore::SimpleLineLayout::createLineRuns):
+        (WebCore::SimpleLineLayout::closeLineEndingAndAdjustRuns):
+        (WebCore::SimpleLineLayout::createTextRuns):
+        (WebCore::SimpleLineLayout::create):
+        (WebCore::SimpleLineLayout::skipWhitespace): Deleted.
+        (WebCore::SimpleLineLayout::textWidth): Deleted.
+        * rendering/SimpleLineLayout.h:
+
 2014-11-04  Jeremy Jones  <jeremyj@apple.com>
 
         Fix build after r138351
index 028d637..f5d84cb 100644 (file)
@@ -214,34 +214,73 @@ struct Style {
 };
 
 template <typename CharacterType>
-static inline unsigned skipWhitespace(const CharacterType* text, unsigned offset, unsigned length, bool preserveNewline, unsigned& spaceCount)
-{
-    spaceCount = 0;
-    for (; offset < length; ++offset) {
-        bool isSpace = text[offset] == ' ';
-        if (!(isSpace || text[offset] == '\t' || (!preserveNewline && text[offset] == '\n')))
-            return offset;
-        if (isSpace)
-            ++spaceCount;
+class FlowContentIterator {
+public:
+    FlowContentIterator(const RenderBlockFlow& flow)
+        : m_flow(flow)
+        , m_style(flow.style())
+        , m_lineBreakIterator(downcast<RenderText>(*flow.firstChild()).text(), flow.style().locale())
+    {
     }
-    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);
+    unsigned findNextBreakablePosition(unsigned position)
+    {
+        String string = m_lineBreakIterator.string();
+        return nextBreakablePosition<CharacterType, false>(m_lineBreakIterator, string.characters<CharacterType>(), string.length(), position);
+    }
 
-    TextRun run(text + from, to - from);
-    run.setXPos(xPosition);
-    run.setCharactersLength(textLength - from);
-    run.setTabSize(!!style.tabWidth, style.tabWidth);
+    unsigned findNextNonWhitespacePosition(unsigned position, unsigned& spaceCount) const
+    {
+        String string = m_lineBreakIterator.string();
+        unsigned length = string.length();
+        const CharacterType* text = string.characters<CharacterType>();
+        spaceCount = 0;
+        for (; position < length; ++position) {
+            bool isSpace = text[position] == ' ';
+            if (!(isSpace || text[position] == '\t' || (!m_style.preserveNewline && text[position] == '\n')))
+                return position;
+            if (isSpace)
+                ++spaceCount;
+        }
+        return length;
+    }
 
-    ASSERT(run.charactersLength() >= run.length());
+    float textWidth(unsigned from, unsigned to, float xPosition) const
+    {
+        String string = m_lineBreakIterator.string();
+        unsigned length = string.length();
+        if (m_style.font.isFixedPitch() || (!from && to == length)) {
+            const RenderText& renderer = downcast<RenderText>(*m_flow.firstChild());
+            return renderer.width(from, to - from, m_style.font, xPosition, nullptr, nullptr);
+        }
+
+        TextRun run(string.characters<CharacterType>() + from, to - from);
+        run.setXPos(xPosition);
+        run.setCharactersLength(length - from);
+        run.setTabSize(!!m_style.tabWidth, m_style.tabWidth);
+        ASSERT(run.charactersLength() >= run.length());
+        return m_style.font.width(run);
+    }
+
+    bool isNewlineCharacter(unsigned position) const
+    {
+        ASSERT(m_lineBreakIterator.string().length() > position);
+        return m_lineBreakIterator.string().at(position) == '\n';
+    }
+
+    bool isEndOfContent(unsigned position) const
+    {
+        return position >= m_lineBreakIterator.string().length();
+    }
+
+    const Style& style() const { return m_style; }
+
+private:
+    const RenderBlockFlow& m_flow;
+    Style m_style;
+    LazyLineBreakIterator m_lineBreakIterator;
+};
 
-    return style.font.width(run);
-}
 
 static float computeLineLeft(ETextAlign textAlign, float availableWidth, float committedWidth, float logicalLeftOffset)
 {
@@ -260,6 +299,7 @@ static float computeLineLeft(ETextAlign textAlign, float availableWidth, float c
     case WEBKIT_CENTER:
         return left + std::max<float>(remainingWidth / 2, 0);
     case JUSTIFY:
+        ASSERT_NOT_REACHED();
         break;
     }
     ASSERT_NOT_REACHED();
@@ -275,7 +315,8 @@ struct TextFragment {
         , isBreakable(false)
         , mustBreak(false)
         , width(0)
-    { }
+    {
+    }
 
     TextFragment(unsigned textStart, unsigned textEnd, float textWidth, bool isWhitespaceOnly)
         : start(textStart)
@@ -285,7 +326,8 @@ struct TextFragment {
         , isBreakable(false)
         , mustBreak(false)
         , width(textWidth)
-    { }
+    {
+    }
 
     bool isEmpty() const
     {
@@ -397,8 +439,9 @@ struct LineState {
 };
 
 template <typename CharacterType>
-static void removeTrailingWhitespace(LineState& lineState, Layout::RunVector& lineRuns, const Style& style, const CharacterType* text, unsigned textLength)
+void removeTrailingWhitespace(LineState& lineState, Layout::RunVector& lineRuns, const FlowContentIterator<CharacterType>& contentIterator)
 {
+    const auto& style = contentIterator.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)) {
@@ -434,12 +477,12 @@ static void removeTrailingWhitespace(LineState& lineState, Layout::RunVector& li
     }
 
     // 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 && lineState.position < textLength && text[lineState.position] == '\n')
+    if (lastPosition != lineState.position && style.preserveNewline && contentIterator.isNewlineCharacter(lineState.position))
         ++lineState.position;
 }
 
 template <typename CharacterType>
-static void initializeNewLine(LineState& lineState, const Style& style, const CharacterType* text, unsigned textLength, unsigned lineStartRunIndex)
+void initializeNewLine(LineState& lineState, const FlowContentIterator<CharacterType>& contentIterator, unsigned lineStartRunIndex)
 {
     lineState.lineStartRunIndex = lineStartRunIndex;
     // Skip leading whitespace if collapsing whitespace, unless there's an uncommitted fragment pushed from the previous line.
@@ -451,14 +494,13 @@ static void initializeNewLine(LineState& lineState, const Style& style, const Ch
             lineState.jumpTo(lineState.oveflowedFragment.start, 0); // Start over with this fragment.
     } else {
         unsigned spaceCount = 0;
-        lineState.jumpTo(style.collapseWhitespace ? skipWhitespace(text, lineState.position, textLength, style.preserveNewline, spaceCount) : lineState.position, 0);
+        lineState.jumpTo(contentIterator.style().collapseWhitespace ? contentIterator.findNextNonWhitespacePosition(lineState.position, spaceCount) : lineState.position, 0);
     }
     lineState.oveflowedFragment = TextFragment();
 }
 
 template <typename CharacterType>
-static TextFragment splitFragmentToFitLine(TextFragment& fragmentToSplit, float availableWidth, bool keepAtLeastOneCharacter, const RenderText& textRenderer,
-    const CharacterType* text, unsigned textLength, const Style& style)
+TextFragment splitFragmentToFitLine(TextFragment& fragmentToSplit, float availableWidth, bool keepAtLeastOneCharacter, const FlowContentIterator<CharacterType>& contentIterator)
 {
     // Fast path for single char fragments.
     if (fragmentToSplit.start + 1 == fragmentToSplit.end) {
@@ -477,7 +519,7 @@ static TextFragment splitFragmentToFitLine(TextFragment& fragmentToSplit, float
     float width = 0;
     while (left < right) {
         unsigned middle = (left + right) / 2;
-        width = textWidth(textRenderer, text, textLength, fragmentToSplit.start, middle + 1, 0, style);
+        width = contentIterator.textWidth(fragmentToSplit.start, middle + 1, 0);
         if (availableWidth > width)
             left = middle + 1;
         else if (availableWidth < width)
@@ -492,7 +534,7 @@ static TextFragment splitFragmentToFitLine(TextFragment& fragmentToSplit, float
         ++right;
     TextFragment fragmentForNextLine(fragmentToSplit);
     fragmentToSplit.end = right;
-    fragmentToSplit.width = fragmentToSplit.isEmpty() ? 0 : textWidth(textRenderer, text, textLength, fragmentToSplit.start, right, 0, style);
+    fragmentToSplit.width = fragmentToSplit.isEmpty() ? 0 : contentIterator.textWidth(fragmentToSplit.start, right, 0);
 
     fragmentForNextLine.start = fragmentToSplit.end;
     fragmentForNextLine.width -= fragmentToSplit.width;
@@ -500,19 +542,19 @@ static TextFragment splitFragmentToFitLine(TextFragment& fragmentToSplit, float
 }
 
 template <typename CharacterType>
-static TextFragment nextFragment(unsigned previousFragmentEnd, LazyLineBreakIterator& lineBreakIterator, const Style& style, const CharacterType* text, unsigned textLength,
-    float xPosition, const RenderText& textRenderer)
+TextFragment nextFragment(unsigned previousFragmentEnd, FlowContentIterator<CharacterType>& contentIterator, 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 = contentIterator.style();
     TextFragment fragment;
-    fragment.mustBreak = style.preserveNewline && text[previousFragmentEnd] == '\n';
+    fragment.mustBreak = style.preserveNewline && contentIterator.isNewlineCharacter(previousFragmentEnd);
     unsigned spaceCount = 0;
     unsigned whitespaceEnd = previousFragmentEnd;
     if (!fragment.mustBreak)
-        whitespaceEnd = skipWhitespace(text, previousFragmentEnd, textLength, style.preserveNewline, spaceCount);
+        whitespaceEnd = contentIterator.findNextNonWhitespacePosition(previousFragmentEnd, spaceCount);
     fragment.isWhitespaceOnly = previousFragmentEnd < whitespaceEnd;
     fragment.start = previousFragmentEnd;
     if (fragment.isWhitespaceOnly)
@@ -520,7 +562,7 @@ static TextFragment nextFragment(unsigned previousFragmentEnd, LazyLineBreakIter
     else if (fragment.mustBreak)
         fragment.end = fragment.start + 1;
     else
-        fragment.end = nextBreakablePosition<CharacterType, false>(lineBreakIterator, text, textLength, previousFragmentEnd + 1);
+        fragment.end = contentIterator.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.
@@ -535,18 +577,18 @@ static TextFragment nextFragment(unsigned previousFragmentEnd, LazyLineBreakIter
     else if (fragmentLength == spaceCount) // Space only.
         fragment.width = style.spaceWidth * spaceCount;
     else
-        fragment.width = textWidth(textRenderer, text, textLength, fragment.start, fragment.end, xPosition, style);
+        fragment.width = contentIterator.textWidth(fragment.start, fragment.end, xPosition);
     return fragment;
 }
 
 template <typename CharacterType>
-bool createLineRuns(LineState& lineState, Layout::RunVector& lineRuns, LazyLineBreakIterator& lineBreakIterator, const Style& style, const CharacterType* text,
-    unsigned textLength, const RenderText& textRenderer)
+bool createLineRuns(LineState& lineState, Layout::RunVector& lineRuns, FlowContentIterator<CharacterType>& contentIterator)
 {
+    const auto& style = contentIterator.style();
     bool lineCanBeWrapped = style.wrapLines || style.breakWordOnOverflow;
-    while (lineState.position < textLength) {
+    while (!contentIterator.isEndOfContent(lineState.position)) {
         // Find the next text fragment. Start from the end of the previous fragment -current line end.
-        TextFragment fragment = nextFragment(lineState.position, lineBreakIterator, style, text, textLength, lineState.width(), textRenderer);
+        TextFragment fragment = nextFragment(lineState.position, contentIterator, 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.
@@ -568,7 +610,7 @@ bool createLineRuns(LineState& lineState, Layout::RunVector& lineRuns, LazyLineB
             } 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, textRenderer, text, textLength, style);
+                lineState.oveflowedFragment = splitFragmentToFitLine(fragment, lineState.availableWidth - lineState.width(), isFirstFragment, contentIterator);
                 if (!fragment.isEmpty()) {
                     // Whitespace fragments can get pushed entirely to the next line.
                     lineState.addUncommitted(fragment);
@@ -593,20 +635,19 @@ bool createLineRuns(LineState& lineState, Layout::RunVector& lineRuns, LazyLineB
             lineState.addUncommitted(fragment);
     }
     lineState.commitAndCreateRun(lineRuns);
-    return lineState.position >= textLength && lineState.oveflowedFragment.isEmpty();
+    return contentIterator.isEndOfContent(lineState.position) && lineState.oveflowedFragment.isEmpty();
 }
 
 template <typename CharacterType>
-static void closeLineEndingAndAdjustRuns(LineState& lineState, Layout::RunVector& lineRuns, unsigned& lineCount,
-    const Style& style, const CharacterType* text, unsigned textLength)
+void closeLineEndingAndAdjustRuns(LineState& lineState, Layout::RunVector& lineRuns, unsigned& lineCount, const FlowContentIterator<CharacterType>& contentIterator)
 {
     if (lineState.lineStartRunIndex == lineRuns.size())
         return;
 
     ASSERT(lineRuns.size());
-    removeTrailingWhitespace(lineState, lineRuns, style, text, textLength);
+    removeTrailingWhitespace(lineState, lineRuns, contentIterator);
     // Adjust runs' position by taking line's alignment into account.
-    if (float lineLogicalLeft = computeLineLeft(style.textAlign, lineState.availableWidth, lineState.committedWidth, lineState.logicalLeftOffset)) {
+    if (float lineLogicalLeft = computeLineLeft(contentIterator.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;
@@ -628,39 +669,35 @@ static void updateLineConstrains(const RenderBlockFlow& flow, float& availableWi
 }
 
 template <typename CharacterType>
-void createTextRuns(Layout::RunVector& runs, unsigned& lineCount, RenderBlockFlow& flow, RenderText& textRenderer)
+void createTextRuns(Layout::RunVector& runs, RenderBlockFlow& flow, unsigned& lineCount)
 {
-    const Style style(flow.style());
-    const CharacterType* text = textRenderer.text()->characters<CharacterType>();
-    const unsigned textLength = textRenderer.textLength();
     LayoutUnit borderAndPaddingBefore = flow.borderAndPaddingBefore();
     LayoutUnit lineHeight = lineHeightFromFlow(flow);
-    LazyLineBreakIterator lineBreakIterator(textRenderer.text(), flow.style().locale());
     LineState lineState;
     bool isEndOfContent = false;
+    FlowContentIterator<CharacterType> contentIterator = FlowContentIterator<CharacterType>(flow);
 
     do {
         flow.setLogicalHeight(lineHeight * lineCount + borderAndPaddingBefore);
         updateLineConstrains(flow, lineState.availableWidth, lineState.logicalLeftOffset);
-        initializeNewLine(lineState, style, text, textLength, runs.size());
-        isEndOfContent = createLineRuns(lineState, runs, lineBreakIterator, style, text, textLength, textRenderer);
-        closeLineEndingAndAdjustRuns(lineState, runs, lineCount, style, text, textLength);
+        initializeNewLine(lineState, contentIterator, runs.size());
+        isEndOfContent = createLineRuns(lineState, runs, contentIterator);
+        closeLineEndingAndAdjustRuns(lineState, runs, lineCount, contentIterator);
     } while (!isEndOfContent);
     ASSERT(!lineState.uncommittedWidth);
 }
 
 std::unique_ptr<Layout> create(RenderBlockFlow& flow)
 {
-    Layout::RunVector runs;
     unsigned lineCount = 0;
-
+    Layout::RunVector runs;
     RenderText& textRenderer = downcast<RenderText>(*flow.firstChild());
     ASSERT(!textRenderer.firstTextBox());
 
     if (textRenderer.is8Bit())
-        createTextRuns<LChar>(runs, lineCount, flow, textRenderer);
+        createTextRuns<LChar>(runs, flow, lineCount);
     else
-        createTextRuns<UChar>(runs, lineCount, flow, textRenderer);
+        createTextRuns<UChar>(runs, flow, lineCount);
 
     textRenderer.clearNeedsLayout();
     return Layout::create(runs, lineCount);
index dfbb019..6698000 100644 (file)
@@ -43,7 +43,9 @@ namespace SimpleLineLayout {
 bool canUseFor(const RenderBlockFlow&);
 
 struct Run {
+#if COMPILER(MSVC)
     Run() { }
+#endif
     Run(unsigned start, unsigned end, float logicalLeft, float logicalRight, bool isEndOfLine)
         : start(start)
         , end(end)