static void revertRuns(Layout::RunVector& runs, unsigned length, float width) { while (length) { ASSERT(runs.size()); Run& lastRun = runs.last(); unsigned lastRunLength = lastRun.end - lastRun.start; if (lastRunLength > length) { lastRun.logicalRight -= width; lastRun.end -= length; break; } length -= lastRunLength; width -= (lastRun.logicalRight - lastRun.logicalLeft); runs.removeLast(); } }
static void closeLineEndingAndAdjustRuns(LineState& line, Layout::RunVector& runs, unsigned previousRunCount, unsigned& lineCount, const TextFragmentIterator& textFragmentIterator) { if (previousRunCount == runs.size()) return; ASSERT(runs.size()); removeTrailingWhitespace(line, runs, textFragmentIterator); if (!runs.size()) return; // Adjust runs' position by taking line's alignment into account. if (float lineLogicalLeft = computeLineLeft(textFragmentIterator.style().textAlign, line.availableWidth(), line.width(), line.logicalLeftOffset())) { for (unsigned i = previousRunCount; i < runs.size(); ++i) { runs[i].logicalLeft += lineLogicalLeft; runs[i].logicalRight += lineLogicalLeft; } } runs.last().isEndOfLine = true; ++lineCount; }
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; }
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); }