void appendFragmentAndCreateRunIfNeeded(const TextFragmentIterator::TextFragment& fragment, Layout::RunVector& runs) { // Adjust end position while collapsing. unsigned endPosition = fragment.isCollapsed() ? fragment.start() + 1 : fragment.end(); // New line needs new run. if (!m_runsWidth) runs.append(Run(fragment.start(), endPosition, m_runsWidth, m_runsWidth + fragment.width(), false)); else { const auto& lastFragment = m_fragments.last(); // Advance last completed fragment when the previous fragment is all set (including multiple parts across renderers) if ((lastFragment.type() != fragment.type()) || !lastFragment.overlapsToNextRenderer()) m_lastCompleteFragment = lastFragment; // Collapse neighbouring whitespace, if they are across multiple renderers and are not collapsed yet. if (lastFragment.isCollapsible() && fragment.isCollapsible()) { ASSERT(lastFragment.isLastInRenderer()); if (!lastFragment.isCollapsed()) { // Line width needs to be reset so that now it takes collapsing into consideration. m_runsWidth -= (lastFragment.width() - m_collapsedWhitespaceWidth); } // This fragment is collapsed completely. No run is needed. return; } if (lastFragment.isLastInRenderer() || lastFragment.isCollapsed()) runs.append(Run(fragment.start(), endPosition, m_runsWidth, m_runsWidth + fragment.width(), false)); else { Run& lastRun = runs.last(); lastRun.end = endPosition; lastRun.logicalRight += fragment.width(); } } m_fragments.append(fragment); m_runsWidth += fragment.width(); if (fragment.type() == TextFragmentIterator::TextFragment::Whitespace) { m_trailingWhitespaceLength += endPosition - fragment.start(); m_trailingWhitespaceWidth += fragment.width(); } else { m_trailingWhitespaceLength = 0; m_trailingWhitespaceWidth = 0; } if (!m_firstCharacterFits) m_firstCharacterFits = fragment.start() + 1 > endPosition || m_runsWidth <= m_availableWidth; }
void createTextRuns(Layout::RunVector& runs, unsigned& lineCount, RenderBlockFlow& flow, RenderText& textRenderer) { 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()); unsigned lineEnd = 0; while (lineEnd < textLength) { if (style.collapseWhitespace) lineEnd = skipWhitespaces(text, lineEnd, textLength, style.preserveNewline); unsigned lineStart = lineEnd; // LineWidth reads the current y position from the flow so keep it updated. flow.setLogicalHeight(lineHeight * lineCount + borderAndPaddingBefore); LineWidth lineWidth(flow, false, DoNotIndentText); auto lineRuns = createLineRuns(lineStart, lineWidth, lineBreakIterator, style, text, textLength, textRenderer); lineEnd = lineRuns.last().end; if (lineStart == lineEnd) continue; lineRuns.last().isEndOfLine = true; float lineLeft = computeLineLeft(style.textAlign, lineWidth); adjustRunOffsets(lineRuns, lineLeft); for (unsigned i = 0; i < lineRuns.size(); ++i) runs.append(lineRuns[i]); ++lineCount; } }
std::unique_ptr<Layout> create(RenderBlockFlow& flow) { RenderText& textRenderer = toRenderText(*flow.firstChild()); ASSERT(!textRenderer.firstTextBox()); const RenderStyle& style = flow.style(); const unsigned textLength = textRenderer.textLength(); ETextAlign textAlign = style.textAlign(); float wordTrailingSpaceWidth = style.font().width(TextRun(&space, 1)); LazyLineBreakIterator lineBreakIterator(textRenderer.text(), style.locale()); int nextBreakable = -1; Layout::RunVector runs; unsigned lineCount = 0; unsigned lineEndOffset = 0; while (lineEndOffset < textLength) { lineEndOffset = skipWhitespaces(textRenderer, lineEndOffset, textLength); unsigned lineStartOffset = lineEndOffset; unsigned wordEndOffset = lineEndOffset; LineWidth lineWidth(flow, false, DoNotIndentText); Vector<Run, 4> lineRuns; lineRuns.uncheckedAppend(Run(lineStartOffset, 0)); while (wordEndOffset < textLength) { ASSERT(!isWhitespace(textRenderer.characterAt(wordEndOffset))); bool previousWasSpaceBetweenWords = wordEndOffset > lineStartOffset && isWhitespace(textRenderer.characterAt(wordEndOffset - 1)); unsigned wordStartOffset = previousWasSpaceBetweenWords ? wordEndOffset - 1 : wordEndOffset; ++wordEndOffset; while (wordEndOffset < textLength) { if (wordEndOffset > lineStartOffset && isBreakable(lineBreakIterator, wordEndOffset, nextBreakable, false)) break; ++wordEndOffset; } unsigned wordLength = wordEndOffset - wordStartOffset; bool includeEndSpace = wordEndOffset < textLength && textRenderer.characterAt(wordEndOffset) == ' '; float wordWidth; if (includeEndSpace) wordWidth = textWidth(textRenderer, wordStartOffset, wordLength + 1, lineWidth.committedWidth(), style) - wordTrailingSpaceWidth; else wordWidth = textWidth(textRenderer, wordStartOffset, wordLength, lineWidth.committedWidth(), style); lineWidth.addUncommittedWidth(wordWidth); // Move to the next line if the current one is full and we have something on it. if (!lineWidth.fitsOnLine() && lineWidth.committedWidth()) break; if (wordStartOffset > lineEndOffset) { // There were more than one consecutive whitespace. ASSERT(previousWasSpaceBetweenWords); // 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(wordStartOffset + 1, lineRuns.last().right)); } lineWidth.commit(); lineRuns.last().right = lineWidth.committedWidth(); lineRuns.last().textLength = wordEndOffset - lineRuns.last().textOffset; lineEndOffset = wordEndOffset; wordEndOffset = skipWhitespaces(textRenderer, wordEndOffset, textLength); if (!lineWidth.fitsOnLine()) { // The first run on the line overflows. ASSERT(lineRuns.size() == 1); break; } } if (lineStartOffset == lineEndOffset) continue; adjustRunOffsets(lineRuns, textAlign, lineWidth.committedWidth(), lineWidth.availableWidth()); for (unsigned i = 0; i < lineRuns.size(); ++i) runs.append(lineRuns[i]); runs.last().isEndOfLine = true; ++lineCount; } textRenderer.clearNeedsLayout(); return Layout::create(runs, lineCount); }