SimpleLineLayoutTextFragmentIterator.cpp [plain text]
#include "config.h"
#include "SimpleLineLayoutTextFragmentIterator.h"
#include "FontCascade.h"
#include "Hyphenation.h"
#include "RenderBlockFlow.h"
#include "RenderChildIterator.h"
#include "SimpleLineLayoutFlowContents.h"
namespace WebCore {
namespace SimpleLineLayout {
TextFragmentIterator::Style::Style(const RenderStyle& style)
: font(style.fontCascade())
, textAlign(style.textAlign())
, hasKerningOrLigatures(font.enableKerning() || font.requiresShaping())
, collapseWhitespace(style.collapseWhiteSpace())
, preserveNewline(style.preserveNewline())
, wrapLines(style.autoWrap())
, breakAnyWordOnOverflow(style.wordBreak() == WordBreak::BreakAll && wrapLines)
, breakFirstWordOnOverflow(breakAnyWordOnOverflow || (style.breakWords() && (wrapLines || preserveNewline)))
, breakNBSP(wrapLines && style.nbspMode() == NBSPMode::Space)
, keepAllWordsForCJK(style.wordBreak() == WordBreak::KeepAll)
, wordSpacing(font.wordSpacing())
, tabWidth(collapseWhitespace ? 0 : style.tabSize())
, shouldHyphenate(style.hyphens() == Hyphens::Auto && canHyphenate(style.locale()))
, hyphenStringWidth(shouldHyphenate ? font.width(TextRun(String(style.hyphenString()))) : 0)
, hyphenLimitBefore(style.hyphenationLimitBefore() < 0 ? 2 : style.hyphenationLimitBefore())
, hyphenLimitAfter(style.hyphenationLimitAfter() < 0 ? 2 : style.hyphenationLimitAfter())
, locale(style.locale())
{
if (style.hyphenationLimitLines() > -1)
hyphenLimitLines = style.hyphenationLimitLines();
}
TextFragmentIterator::TextFragmentIterator(const RenderBlockFlow& flow)
: m_flowContents(flow)
, m_currentSegment(m_flowContents.begin())
, m_lineBreakIterator(m_currentSegment->text, flow.style().locale())
, m_style(flow.style())
{
}
TextFragmentIterator::TextFragment TextFragmentIterator::nextTextFragment(float xPosition)
{
TextFragmentIterator::TextFragment nextFragment = findNextTextFragment(xPosition);
m_atEndOfSegment = (m_currentSegment == m_flowContents.end()) || (m_position == m_currentSegment->end);
return nextFragment;
}
TextFragmentIterator::TextFragment TextFragmentIterator::findNextTextFragment(float xPosition)
{
ASSERT(m_currentSegment != m_flowContents.end());
unsigned startPosition = m_position;
if (m_atEndOfSegment)
++m_currentSegment;
if (m_currentSegment == m_flowContents.end())
return TextFragment(startPosition, startPosition, 0, TextFragment::ContentEnd);
if (isHardLineBreak(m_currentSegment))
return TextFragment(startPosition, startPosition, 0, TextFragment::HardLineBreak);
if (isSoftLineBreak(startPosition)) {
unsigned endPosition = ++m_position;
return TextFragment(startPosition, endPosition, 0, TextFragment::SoftLineBreak);
}
float width = 0;
bool overlappingFragment = false;
unsigned endPosition = skipToNextPosition(PositionType::NonWhitespace, startPosition, width, xPosition, overlappingFragment);
unsigned segmentEndPosition = m_currentSegment->end;
ASSERT(startPosition <= endPosition);
if (startPosition < endPosition) {
bool multipleWhitespace = startPosition + 1 < endPosition;
bool isCollapsed = multipleWhitespace && m_style.collapseWhitespace;
m_position = endPosition;
return TextFragment(startPosition, endPosition, width, TextFragment::Whitespace, endPosition == segmentEndPosition, false, isCollapsed, m_style.collapseWhitespace);
}
endPosition = skipToNextPosition(PositionType::Breakable, startPosition, width, xPosition, overlappingFragment);
m_position = endPosition;
return TextFragment(startPosition, endPosition, width, TextFragment::NonWhitespace, endPosition == segmentEndPosition, overlappingFragment, false, false);
}
void TextFragmentIterator::revertToEndOfFragment(const TextFragment& fragment)
{
ASSERT(m_position >= fragment.end());
while (m_currentSegment->start > fragment.end())
--m_currentSegment;
m_position = fragment.end();
m_atEndOfSegment = false;
}
static inline unsigned nextBreakablePositionInSegment(LazyLineBreakIterator& lineBreakIterator, unsigned startPosition, bool breakNBSP, bool keepAllWordsForCJK)
{
if (keepAllWordsForCJK) {
if (breakNBSP)
return nextBreakablePositionKeepingAllWords(lineBreakIterator, startPosition);
return nextBreakablePositionKeepingAllWordsIgnoringNBSP(lineBreakIterator, startPosition);
}
if (lineBreakIterator.mode() == LineBreakIteratorMode::Default) {
if (breakNBSP)
return WebCore::nextBreakablePosition(lineBreakIterator, startPosition);
return nextBreakablePositionIgnoringNBSP(lineBreakIterator, startPosition);
}
if (breakNBSP)
return nextBreakablePositionWithoutShortcut(lineBreakIterator, startPosition);
return nextBreakablePositionIgnoringNBSPWithoutShortcut(lineBreakIterator, startPosition);
}
unsigned TextFragmentIterator::nextBreakablePosition(const FlowContents::Segment& segment, unsigned startPosition)
{
ASSERT(startPosition < segment.end);
StringView currentText = m_lineBreakIterator.stringView();
StringView segmentText = StringView(segment.text);
if (segmentText != currentText) {
unsigned textLength = currentText.length();
UChar lastCharacter = textLength > 0 ? currentText[textLength - 1] : 0;
UChar secondToLastCharacter = textLength > 1 ? currentText[textLength - 2] : 0;
m_lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
m_lineBreakIterator.resetStringAndReleaseIterator(segment.text, m_style.locale, LineBreakIteratorMode::Default);
}
return segment.toRenderPosition(nextBreakablePositionInSegment(m_lineBreakIterator, segment.toSegmentPosition(startPosition), m_style.breakNBSP, m_style.keepAllWordsForCJK));
}
unsigned TextFragmentIterator::nextNonWhitespacePosition(const FlowContents::Segment& segment, unsigned startPosition)
{
ASSERT(startPosition < segment.end);
unsigned position = startPosition;
for (; position < segment.end; ++position) {
auto character = segment.text[segment.toSegmentPosition(position)];
bool isWhitespace = character == ' ' || character == '\t' || (!m_style.preserveNewline && character == '\n');
if (!isWhitespace)
return position;
}
return position;
}
Optional<unsigned> TextFragmentIterator::lastHyphenPosition(const TextFragmentIterator::TextFragment& run, unsigned before) const
{
ASSERT(run.start() < before);
auto& segment = *m_currentSegment;
ASSERT(segment.start <= before && before <= segment.end);
ASSERT(is<RenderText>(segment.renderer));
if (!m_style.shouldHyphenate || run.type() != TextFragment::NonWhitespace)
return WTF::nullopt;
unsigned runLength = run.end() - run.start();
if (m_style.hyphenLimitBefore >= runLength || m_style.hyphenLimitAfter >= runLength || m_style.hyphenLimitBefore + m_style.hyphenLimitAfter > runLength)
return WTF::nullopt;
auto runStart = segment.toSegmentPosition(run.start());
auto beforeIndex = segment.toSegmentPosition(before) - runStart;
if (beforeIndex <= m_style.hyphenLimitBefore)
return WTF::nullopt;
beforeIndex = std::min(beforeIndex, runLength - m_style.hyphenLimitAfter + 1);
auto substringForHyphenation = StringView(segment.text).substring(runStart, run.end() - run.start());
auto hyphenLocation = lastHyphenLocation(substringForHyphenation, beforeIndex, m_style.locale);
if (hyphenLocation && hyphenLocation >= m_style.hyphenLimitBefore && m_style.hyphenLimitAfter <= (runLength - hyphenLocation))
return segment.toRenderPosition(hyphenLocation + runStart);
return WTF::nullopt;
}
unsigned TextFragmentIterator::skipToNextPosition(PositionType positionType, unsigned startPosition, float& width, float xPosition, bool& overlappingFragment)
{
overlappingFragment = false;
unsigned currentPosition = startPosition;
unsigned nextPosition = currentPosition;
if (positionType == NonWhitespace)
nextPosition = nextNonWhitespacePosition(*m_currentSegment, currentPosition);
else if (positionType == Breakable) {
nextPosition = nextBreakablePosition(*m_currentSegment, currentPosition);
bool skipCurrentPosition = nextPosition == currentPosition;
if (skipCurrentPosition) {
ASSERT(currentPosition < m_currentSegment->end);
if (currentPosition == m_currentSegment->end - 1)
nextPosition = m_currentSegment->end;
else
nextPosition = nextBreakablePosition(*m_currentSegment, currentPosition + 1);
}
if (nextPosition == m_currentSegment->end) {
const auto nextSegment = m_currentSegment + 1;
if (nextSegment != m_flowContents.end() && !isHardLineBreak(nextSegment))
overlappingFragment = nextPosition < nextBreakablePosition(*nextSegment, nextPosition);
}
}
width = 0;
if (nextPosition == currentPosition)
return currentPosition;
bool measureText = positionType != NonWhitespace || !m_style.collapseWhitespace;
if (measureText)
width = this->textWidth(currentPosition, nextPosition, xPosition);
else if (startPosition < nextPosition)
width = m_style.font.spaceWidth() + m_style.wordSpacing;
return nextPosition;
}
float TextFragmentIterator::textWidth(unsigned from, unsigned to, float xPosition) const
{
auto& segment = *m_currentSegment;
ASSERT(segment.start <= from && from <= segment.end && segment.start <= to && to <= segment.end);
ASSERT(is<RenderText>(segment.renderer));
if (!m_style.font.size() || from == to)
return 0;
unsigned segmentFrom = segment.toSegmentPosition(from);
unsigned segmentTo = segment.toSegmentPosition(to);
if (m_style.font.isFixedPitch())
return downcast<RenderText>(segment.renderer).width(segmentFrom, segmentTo - segmentFrom, m_style.font, xPosition, nullptr, nullptr);
bool measureWithEndSpace = m_style.hasKerningOrLigatures && m_style.collapseWhitespace
&& segmentTo < segment.text.length() && segment.text[segmentTo] == ' ';
if (measureWithEndSpace)
++segmentTo;
float width = 0;
if (segment.canUseSimplifiedTextMeasuring)
width = m_style.font.widthForSimpleText(StringView(segment.text).substring(segmentFrom, segmentTo - segmentFrom));
else {
TextRun run(StringView(segment.text).substring(segmentFrom, segmentTo - segmentFrom), xPosition);
if (m_style.tabWidth)
run.setTabSize(true, m_style.tabWidth);
width = m_style.font.width(run);
}
if (measureWithEndSpace)
width -= (m_style.font.spaceWidth() + m_style.wordSpacing);
return std::max<float>(0, width);
}
}
}