InlineLineBuilder.cpp [plain text]
#include "config.h"
#include "InlineLineBuilder.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "FloatingContext.h"
#include "InlineFormattingContext.h"
#include "LayoutBox.h"
#include "LayoutBoxGeometry.h"
#include "LayoutState.h"
#include "TextUtil.h"
#include <wtf/unicode/CharacterNames.h>
namespace WebCore {
namespace Layout {
static inline bool endsWithSoftWrapOpportunity(const InlineTextItem& currentTextItem, const InlineTextItem& nextInlineTextItem)
{
ASSERT(!nextInlineTextItem.isWhitespace());
if (currentTextItem.isWhitespace())
return true;
if (¤tTextItem.inlineTextBox() == &nextInlineTextItem.inlineTextBox())
return true;
auto previousContent = currentTextItem.inlineTextBox().content();
auto lineBreakIterator = LazyLineBreakIterator { nextInlineTextItem.inlineTextBox().content() };
auto previousContentLength = previousContent.length();
UChar lastCharacter = previousContentLength ? previousContent[previousContentLength - 1] : 0;
if (lastCharacter == softHyphen && currentTextItem.style().hyphens() == Hyphens::None)
return false;
UChar secondToLastCharacter = previousContentLength > 1 ? previousContent[previousContentLength - 2] : 0;
lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
return !TextUtil::findNextBreakablePosition(lineBreakIterator, 0, nextInlineTextItem.style());
}
static inline bool isAtSoftWrapOpportunity(const InlineFormattingContext& inlineFormattingContext, const InlineItem& current, const InlineItem& next)
{
ASSERT(current.isText() || current.isBox() || current.isFloat());
ASSERT(next.isText() || next.isBox() || next.isFloat());
if (current.isText() && next.isText()) {
auto& currentInlineTextItem = downcast<InlineTextItem>(current);
auto& nextInlineTextItem = downcast<InlineTextItem>(next);
if (currentInlineTextItem.isWhitespace()) {
return true;
}
if (nextInlineTextItem.isWhitespace()) {
return nextInlineTextItem.style().whiteSpace() != WhiteSpace::BreakSpaces;
}
if (current.style().lineBreak() == LineBreak::Anywhere || next.style().lineBreak() == LineBreak::Anywhere) {
return true;
}
return endsWithSoftWrapOpportunity(currentInlineTextItem, nextInlineTextItem);
}
if (current.isFloat() || next.isFloat()) {
return true;
}
if (current.isBox() || next.isBox()) {
auto isImageContent = current.layoutBox().isImage() || next.layoutBox().isImage();
if (isImageContent)
return inlineFormattingContext.quirks().hasSoftWrapOpportunityAtImage();
return true;
}
ASSERT_NOT_REACHED();
return true;
}
struct LineCandidate {
LineCandidate(bool ignoreTrailingLetterSpacing);
void reset();
struct InlineContent {
InlineContent(bool ignoreTrailingLetterSpacing);
const InlineContentBreaker::ContinuousContent& continuousContent() const { return m_continuousContent; }
const InlineItem* trailingLineBreak() const { return m_trailingLineBreak; }
const InlineItem* trailingWordBreakOpportunity() const { return m_trailingWordBreakOpportunity; }
void appendInlineItem(const InlineItem&, InlineLayoutUnit logicalWidth);
void appendTrailingLineBreak(const InlineItem& lineBreakItem) { m_trailingLineBreak = &lineBreakItem; }
void appendtrailingWordBreakOpportunity(const InlineItem& wordBreakItem) { m_trailingWordBreakOpportunity = &wordBreakItem; }
void reset();
bool isEmpty() const { return m_continuousContent.runs().isEmpty() && !trailingWordBreakOpportunity() && !trailingLineBreak(); }
bool hasInlineLevelBox() const { return m_hasInlineLevelBox; }
private:
bool m_ignoreTrailingLetterSpacing { false };
InlineContentBreaker::ContinuousContent m_continuousContent;
const InlineItem* m_trailingLineBreak { nullptr };
const InlineItem* m_trailingWordBreakOpportunity { nullptr };
bool m_hasInlineLevelBox { false };
};
InlineContent inlineContent;
const InlineItem* floatItem { nullptr };
};
LineCandidate::LineCandidate(bool ignoreTrailingLetterSpacing)
: inlineContent(ignoreTrailingLetterSpacing)
{
}
LineCandidate::InlineContent::InlineContent(bool ignoreTrailingLetterSpacing)
: m_ignoreTrailingLetterSpacing(ignoreTrailingLetterSpacing)
{
}
inline void LineCandidate::InlineContent::appendInlineItem(const InlineItem& inlineItem, InlineLayoutUnit logicalWidth)
{
ASSERT(inlineItem.isText() || inlineItem.isBox() || inlineItem.isInlineBoxStart() || inlineItem.isInlineBoxEnd());
auto collapsibleWidth = [&]() -> Optional<InlineLayoutUnit> {
if (!inlineItem.isText())
return { };
auto& inlineTextItem = downcast<InlineTextItem>(inlineItem);
if (inlineTextItem.isWhitespace() && !InlineTextItem::shouldPreserveSpacesAndTabs(inlineTextItem)) {
return logicalWidth;
}
if (m_ignoreTrailingLetterSpacing)
return { };
auto letterSpacing = inlineItem.style().letterSpacing();
if (letterSpacing <= 0)
return { };
ASSERT(logicalWidth > letterSpacing);
return letterSpacing;
};
m_continuousContent.append(inlineItem, logicalWidth, collapsibleWidth());
m_hasInlineLevelBox = m_hasInlineLevelBox || inlineItem.isBox() || inlineItem.isInlineBoxStart();
}
inline void LineCandidate::InlineContent::reset()
{
m_continuousContent.reset();
m_trailingLineBreak = { };
m_trailingWordBreakOpportunity = { };
m_hasInlineLevelBox = false;
}
inline void LineCandidate::reset()
{
floatItem = nullptr;
inlineContent.reset();
}
InlineLayoutUnit LineBuilder::inlineItemWidth(const InlineItem& inlineItem, InlineLayoutUnit contentLogicalLeft) const
{
if (is<InlineTextItem>(inlineItem)) {
auto& inlineTextItem = downcast<InlineTextItem>(inlineItem);
if (auto contentWidth = inlineTextItem.width())
return *contentWidth;
if (!inlineTextItem.isWhitespace() || InlineTextItem::shouldPreserveSpacesAndTabs(inlineTextItem))
return TextUtil::width(inlineTextItem, contentLogicalLeft);
return TextUtil::width(inlineTextItem, inlineTextItem.start(), inlineTextItem.start() + 1, contentLogicalLeft);
}
if (inlineItem.isLineBreak() || inlineItem.isWordBreakOpportunity())
return { };
auto& layoutBox = inlineItem.layoutBox();
auto& boxGeometry = m_inlineFormattingContext.geometryForBox(layoutBox);
if (layoutBox.isFloatingPositioned())
return boxGeometry.marginBoxWidth();
if (layoutBox.isReplacedBox())
return boxGeometry.marginBoxWidth();
if (inlineItem.isInlineBoxStart())
return boxGeometry.marginStart() + boxGeometry.borderLeft() + boxGeometry.paddingLeft().valueOr(0);
if (inlineItem.isInlineBoxEnd())
return boxGeometry.marginEnd() + boxGeometry.borderRight() + boxGeometry.paddingRight().valueOr(0);
return boxGeometry.marginBoxWidth();
}
LineBuilder::LineBuilder(InlineFormattingContext& inlineFormattingContext, FloatingState& floatingState, HorizontalConstraints rootHorizontalConstraints, const InlineItems& inlineItems)
: m_inlineFormattingContext(inlineFormattingContext)
, m_inlineFormattingState(&inlineFormattingContext.formattingState())
, m_floatingState(&floatingState)
, m_rootHorizontalConstraints(rootHorizontalConstraints)
, m_line(inlineFormattingContext)
, m_inlineItems(inlineItems)
{
}
LineBuilder::LineBuilder(const InlineFormattingContext& inlineFormattingContext, const InlineItems& inlineItems)
: m_inlineFormattingContext(inlineFormattingContext)
, m_line(inlineFormattingContext)
, m_inlineItems(inlineItems)
{
}
LineBuilder::LineContent LineBuilder::layoutInlineContent(const InlineItemRange& needsLayoutRange, size_t partialLeadingContentLength, Optional<InlineLayoutUnit> overflowLogicalWidth, const InlineRect& initialLineLogicalRect, bool isFirstLine)
{
initialize(initialConstraintsForLine(initialLineLogicalRect, isFirstLine));
auto committedContent = placeInlineContent(needsLayoutRange, partialLeadingContentLength, overflowLogicalWidth);
auto committedRange = close(needsLayoutRange, committedContent);
auto isLastLine = isLastLineWithInlineContent(committedRange, needsLayoutRange.end, committedContent.partialTrailingContentLength);
return LineContent { committedRange, committedContent.partialTrailingContentLength, committedContent.overflowLogicalWidth, m_floats, m_contentIsConstrainedByFloat
, m_lineLogicalRect.topLeft()
, m_lineLogicalRect.width()
, m_line.contentLogicalWidth()
, m_line.isConsideredEmpty()
, isLastLine
, m_line.runs()};
}
LineBuilder::IntrinsicContent LineBuilder::computedIntrinsicWidth(const InlineItemRange& needsLayoutRange, InlineLayoutUnit availableWidth)
{
initialize({ { { }, { availableWidth, maxInlineLayoutUnit() } }, false });
auto committedContent = placeInlineContent(needsLayoutRange, { }, { });
auto committedRange = close(needsLayoutRange, committedContent);
return { committedRange, m_line.contentLogicalWidth(), m_floats };
}
void LineBuilder::initialize(const UsedConstraints& lineConstraints)
{
m_floats.clear();
m_partialLeadingTextItem = { };
m_wrapOpportunityList.clear();
m_line.initialize();
m_lineLogicalRect = lineConstraints.logicalRect;
m_contentIsConstrainedByFloat = lineConstraints.isConstrainedByFloat;
}
LineBuilder::CommittedContent LineBuilder::placeInlineContent(const InlineItemRange& needsLayoutRange, size_t partialLeadingContentLength, Optional<InlineLayoutUnit> leadingLogicalWidth)
{
auto lineCandidate = LineCandidate { layoutState().shouldIgnoreTrailingLetterSpacing() };
auto inlineContentBreaker = InlineContentBreaker { };
auto currentItemIndex = needsLayoutRange.start;
size_t committedInlineItemCount = 0;
while (currentItemIndex < needsLayoutRange.end) {
candidateContentForLine(lineCandidate, currentItemIndex, needsLayoutRange, partialLeadingContentLength, std::exchange(leadingLogicalWidth, WTF::nullopt), m_line.contentLogicalRight());
auto result = Result { };
if (lineCandidate.floatItem) {
ASSERT(lineCandidate.inlineContent.isEmpty());
handleFloatContent(*lineCandidate.floatItem);
} else
result = handleInlineContent(inlineContentBreaker, needsLayoutRange, lineCandidate);
committedInlineItemCount = result.committedCount.isRevert ? result.committedCount.value : committedInlineItemCount + result.committedCount.value;
auto& inlineContent = lineCandidate.inlineContent;
auto inlineContentIsFullyCommitted = inlineContent.continuousContent().runs().size() == result.committedCount.value && !result.partialTrailingContentLength;
auto isEndOfLine = result.isEndOfLine == InlineContentBreaker::IsEndOfLine::Yes;
if (inlineContentIsFullyCommitted) {
if (auto* wordBreakOpportunity = inlineContent.trailingWordBreakOpportunity()) {
++committedInlineItemCount;
m_line.append(*wordBreakOpportunity, { });
}
if (inlineContent.trailingLineBreak()) {
m_line.append(*inlineContent.trailingLineBreak(), { });
++committedInlineItemCount;
isEndOfLine = true;
}
}
if (isEndOfLine) {
return { committedInlineItemCount, result.partialTrailingContentLength, result.overflowLogicalWidth };
}
currentItemIndex = needsLayoutRange.start + committedInlineItemCount + m_floats.size();
partialLeadingContentLength = { };
}
return { committedInlineItemCount, { } };
}
LineBuilder::InlineItemRange LineBuilder::close(const InlineItemRange& needsLayoutRange, const CommittedContent& committedContent)
{
ASSERT(committedContent.inlineItemCount || !m_floats.isEmpty() || m_contentIsConstrainedByFloat);
auto numberOfCommittedItems = committedContent.inlineItemCount + m_floats.size();
auto trailingInlineItemIndex = needsLayoutRange.start + numberOfCommittedItems - 1;
auto lineRange = InlineItemRange { needsLayoutRange.start, trailingInlineItemIndex + 1 };
ASSERT(lineRange.end <= needsLayoutRange.end);
if (!committedContent.inlineItemCount) {
return lineRange;
}
auto availableWidth = m_lineLogicalRect.width() - m_line.contentLogicalRight();
m_line.removeCollapsibleContent(availableWidth);
auto horizontalAlignment = root().style().textAlign();
auto runsExpandHorizontally = horizontalAlignment == TextAlignMode::Justify && !isLastLineWithInlineContent(lineRange, needsLayoutRange.end, committedContent.partialTrailingContentLength);
if (runsExpandHorizontally)
m_line.applyRunExpansion(m_lineLogicalRect.width() - m_line.contentLogicalRight());
auto lineEndsWithHyphen = false;
if (!m_line.isConsideredEmpty()) {
ASSERT(!m_line.runs().isEmpty());
auto& lastTextContent = m_line.runs().last().textContent();
lineEndsWithHyphen = lastTextContent && lastTextContent->needsHyphen();
}
m_successiveHyphenatedLineCount = lineEndsWithHyphen ? m_successiveHyphenatedLineCount + 1 : 0;
return lineRange;
}
Optional<HorizontalConstraints> LineBuilder::floatConstraints(const InlineRect& lineLogicalRect) const
{
auto* floatingState = this->floatingState();
if (!floatingState || floatingState->floats().isEmpty())
return { };
auto floatingContext = FloatingContext { formattingContext(), *floatingState };
auto constraints = floatingContext.constraints(toLayoutUnit(lineLogicalRect.top()), toLayoutUnit(lineLogicalRect.bottom()));
if (constraints.left && constraints.left->x <= lineLogicalRect.left())
constraints.left = { };
if (constraints.right && constraints.right->x >= lineLogicalRect.right())
constraints.right = { };
if (!constraints.left && !constraints.right)
return { };
auto lineLogicalLeft = lineLogicalRect.left();
auto lineLogicalRight = lineLogicalRect.right();
if (constraints.left && constraints.right) {
ASSERT(constraints.left->x <= constraints.right->x);
lineLogicalRight = constraints.right->x;
lineLogicalLeft = constraints.left->x;
} else if (constraints.left) {
ASSERT(constraints.left->x >= lineLogicalLeft);
lineLogicalLeft = constraints.left->x;
} else if (constraints.right) {
lineLogicalRight = std::max<InlineLayoutUnit>(lineLogicalLeft, constraints.right->x);
}
return HorizontalConstraints { toLayoutUnit(lineLogicalLeft), toLayoutUnit(lineLogicalRight - lineLogicalLeft) };
}
LineBuilder::UsedConstraints LineBuilder::initialConstraintsForLine(const InlineRect& initialLineLogicalRect, bool isFirstLine) const
{
auto lineLogicalLeft = initialLineLogicalRect.left();
auto lineLogicalRight = initialLineLogicalRect.right();
auto lineIsConstrainedByFloat = false;
if (auto lineConstraints = floatConstraints(initialLineLogicalRect)) {
lineLogicalLeft = lineConstraints->logicalLeft;
lineLogicalRight = lineConstraints->logicalRight();
lineIsConstrainedByFloat = true;
}
auto computedTextIndent = [&]() -> InlineLayoutUnit {
auto& root = this->root();
auto isFormattingContextRootCandidateToTextIndent = !root.isAnonymous();
if (root.isAnonymous()) {
auto isIntegratedRootBoxFirstChild = layoutState().isIntegratedRootBoxFirstChild();
if (isIntegratedRootBoxFirstChild == LayoutState::IsIntegratedRootBoxFirstChild::NotApplicable)
isFormattingContextRootCandidateToTextIndent = root.parent().firstInFlowChild() == &root;
else
isFormattingContextRootCandidateToTextIndent = isIntegratedRootBoxFirstChild == LayoutState::IsIntegratedRootBoxFirstChild::Yes;
}
if (!isFormattingContextRootCandidateToTextIndent)
return { };
auto invertLineRange = false;
#if ENABLE(CSS3_TEXT)
invertLineRange = root.style().textIndentType() == TextIndentType::Hanging;
#endif
auto shouldIndent = invertLineRange != isFirstLine;
if (!shouldIndent)
return { };
auto textIndent = root.style().textIndent();
if (textIndent == RenderStyle::initialTextIndent())
return { };
return { minimumValueForLength(textIndent, initialLineLogicalRect.width()) };
};
lineLogicalLeft += computedTextIndent();
return UsedConstraints { { initialLineLogicalRect.top(), lineLogicalLeft, lineLogicalRight - lineLogicalLeft, initialLineLogicalRect.height() }, lineIsConstrainedByFloat };
}
void LineBuilder::candidateContentForLine(LineCandidate& lineCandidate, size_t currentInlineItemIndex, const InlineItemRange& layoutRange, size_t partialLeadingContentLength, Optional<InlineLayoutUnit> leadingLogicalWidth, InlineLayoutUnit currentLogicalRight)
{
ASSERT(currentInlineItemIndex < layoutRange.end);
lineCandidate.reset();
auto softWrapOpportunityIndex = nextWrapOpportunity(currentInlineItemIndex, layoutRange);
ASSERT(softWrapOpportunityIndex <= layoutRange.end);
if (partialLeadingContentLength) {
m_partialLeadingTextItem = downcast<InlineTextItem>(m_inlineItems[currentInlineItemIndex]).right(partialLeadingContentLength);
auto itemWidth = leadingLogicalWidth ? *std::exchange(leadingLogicalWidth, WTF::nullopt) : inlineItemWidth(*m_partialLeadingTextItem, currentLogicalRight);
lineCandidate.inlineContent.appendInlineItem(*m_partialLeadingTextItem, itemWidth);
currentLogicalRight += itemWidth;
++currentInlineItemIndex;
}
for (auto index = currentInlineItemIndex; index < softWrapOpportunityIndex; ++index) {
auto& inlineItem = m_inlineItems[index];
if (inlineItem.isFloat()) {
lineCandidate.floatItem = &inlineItem;
ASSERT(currentInlineItemIndex + 1 == softWrapOpportunityIndex);
continue;
}
if (inlineItem.isText() || inlineItem.isInlineBoxStart() || inlineItem.isInlineBoxEnd() || inlineItem.isBox()) {
ASSERT(!leadingLogicalWidth || inlineItem.isText());
auto logicalWidth = leadingLogicalWidth ? *std::exchange(leadingLogicalWidth, WTF::nullopt) : inlineItemWidth(inlineItem, currentLogicalRight);
lineCandidate.inlineContent.appendInlineItem(inlineItem, logicalWidth);
currentLogicalRight += logicalWidth;
continue;
}
if (inlineItem.isWordBreakOpportunity()) {
ASSERT(index == softWrapOpportunityIndex - 1);
lineCandidate.inlineContent.appendtrailingWordBreakOpportunity(inlineItem);
continue;
}
if (inlineItem.isLineBreak()) {
ASSERT(index == softWrapOpportunityIndex - 1);
lineCandidate.inlineContent.appendTrailingLineBreak(inlineItem);
continue;
}
ASSERT_NOT_REACHED();
}
}
size_t LineBuilder::nextWrapOpportunity(size_t startIndex, const LineBuilder::InlineItemRange& layoutRange) const
{
auto previousInlineItemIndex = Optional<size_t> { };
for (auto index = startIndex; index < layoutRange.end; ++index) {
auto& inlineItem = m_inlineItems[index];
if (inlineItem.isLineBreak() || inlineItem.isWordBreakOpportunity()) {
return ++index;
}
if (inlineItem.isInlineBoxStart() || inlineItem.isInlineBoxEnd()) {
continue;
}
ASSERT(inlineItem.isText() || inlineItem.isBox() || inlineItem.isFloat());
if (!previousInlineItemIndex) {
previousInlineItemIndex = index;
continue;
}
auto& previousItem = m_inlineItems[*previousInlineItemIndex];
auto& currentItem = m_inlineItems[index];
if (isAtSoftWrapOpportunity(m_inlineFormattingContext, previousItem, currentItem)) {
if (!previousItem.isText() || !currentItem.isText())
return index;
for (auto candidateIndex = *previousInlineItemIndex + 1; candidateIndex < index; ++candidateIndex) {
if (m_inlineItems[candidateIndex].isInlineBoxStart()) {
return candidateIndex;
}
}
return index;
}
previousInlineItemIndex = index;
}
return layoutRange.end;
}
void LineBuilder::handleFloatContent(const InlineItem& floatItem)
{
auto& floatBox = floatItem.layoutBox();
m_floats.append(&floatBox);
auto* floatingState = this->floatingState();
if (!floatingState)
return;
ASSERT(formattingState());
auto& boxGeometry = formattingState()->boxGeometry(floatBox);
boxGeometry.setLogicalTopLeft(LayoutPoint { m_lineLogicalRect.topLeft() });
ASSERT(m_rootHorizontalConstraints);
auto floatingContext = FloatingContext { formattingContext(), *floatingState };
auto floatingPosition = floatingContext.positionForFloat(floatBox, *m_rootHorizontalConstraints);
boxGeometry.setLogicalTopLeft(floatingPosition);
floatingState->append(floatingContext.toFloatItem(floatBox));
if (floatingPosition.y() > m_lineLogicalRect.bottom())
return;
m_contentIsConstrainedByFloat = true;
auto floatBoxWidth = inlineItemWidth(floatItem, { });
if (floatBox.isLeftFloatingPositioned())
m_lineLogicalRect.setLeft(m_lineLogicalRect.left() + floatBoxWidth);
m_lineLogicalRect.expandHorizontally(-floatBoxWidth);
}
LineBuilder::Result LineBuilder::handleInlineContent(InlineContentBreaker& inlineContentBreaker, const InlineItemRange& layoutRange, const LineCandidate& lineCandidate)
{
auto& inlineContent = lineCandidate.inlineContent;
if (inlineContent.continuousContent().runs().isEmpty()) {
ASSERT(inlineContent.trailingLineBreak() || inlineContent.trailingWordBreakOpportunity());
return { inlineContent.trailingLineBreak() ? InlineContentBreaker::IsEndOfLine::Yes : InlineContentBreaker::IsEndOfLine::No };
}
auto shouldDisableHyphenation = [&] {
auto& style = root().style();
unsigned limitLines = style.hyphenationLimitLines() == RenderStyle::initialHyphenationLimitLines() ? std::numeric_limits<unsigned>::max() : style.hyphenationLimitLines();
return m_successiveHyphenatedLineCount >= limitLines;
};
if (shouldDisableHyphenation())
inlineContentBreaker.setHyphenationDisabled();
auto& continuousInlineContent = lineCandidate.inlineContent.continuousContent();
auto lineLogicalRectForCandidateContent = [&] {
if (!floatingState() || !inlineContent.hasInlineLevelBox())
return m_lineLogicalRect;
auto maximumLineLogicalHeight = m_lineLogicalRect.height();
for (auto& run : continuousInlineContent.runs()) {
if (!run.inlineItem.isBox())
continue;
maximumLineLogicalHeight = std::max(maximumLineLogicalHeight, InlineLayoutUnit { formattingContext().geometryForBox(run.inlineItem.layoutBox()).marginBoxHeight() });
}
if (maximumLineLogicalHeight == m_lineLogicalRect.height())
return m_lineLogicalRect;
auto adjustedLineLogicalRect = InlineRect { m_lineLogicalRect.top(), m_lineLogicalRect.left(), m_lineLogicalRect.width(), maximumLineLogicalHeight };
if (auto horizontalConstraints = floatConstraints(adjustedLineLogicalRect)) {
adjustedLineLogicalRect.setLeft(horizontalConstraints->logicalLeft);
adjustedLineLogicalRect.setWidth(horizontalConstraints->logicalWidth);
}
return adjustedLineLogicalRect;
}();
auto availableWidth = lineLogicalRectForCandidateContent.width() - m_line.contentLogicalRight();
auto isLineConsideredEmpty = m_line.isConsideredEmpty() && !m_contentIsConstrainedByFloat;
auto lineStatus = InlineContentBreaker::LineStatus { m_line.contentLogicalRight(), availableWidth, m_line.trimmableTrailingWidth(), m_line.trailingSoftHyphenWidth(), m_line.isTrailingRunFullyTrimmable(), isLineConsideredEmpty };
auto result = inlineContentBreaker.processInlineContent(continuousInlineContent, lineStatus);
if (result.lastWrapOpportunityItem)
m_wrapOpportunityList.append(result.lastWrapOpportunityItem);
auto& candidateRuns = continuousInlineContent.runs();
if (result.action == InlineContentBreaker::Result::Action::Keep) {
m_lineLogicalRect = lineLogicalRectForCandidateContent;
for (auto& run : candidateRuns)
m_line.append(run.inlineItem, run.logicalWidth);
return { result.isEndOfLine, { candidateRuns.size(), false } };
}
auto eligibleOverflowWidthAsLeading = [&] () -> Optional<InlineLayoutUnit> {
ASSERT(result.action == InlineContentBreaker::Result::Action::Wrap || result.action == InlineContentBreaker::Result::Action::Break);
if (candidateRuns.size() != 1 || !candidateRuns.first().inlineItem.isText())
return { };
auto& inlineTextItem = downcast<InlineTextItem>(candidateRuns.first().inlineItem);
if (inlineTextItem.isWhitespace())
return { };
if (result.action == InlineContentBreaker::Result::Action::Wrap)
return candidateRuns.first().logicalWidth;
if (result.action == InlineContentBreaker::Result::Action::Break && result.partialTrailingContent->partialRun)
return candidateRuns.first().logicalWidth - result.partialTrailingContent->partialRun->logicalWidth;
return { };
};
if (result.action == InlineContentBreaker::Result::Action::Wrap) {
ASSERT(result.isEndOfLine == InlineContentBreaker::IsEndOfLine::Yes);
return { InlineContentBreaker::IsEndOfLine::Yes, { }, { }, eligibleOverflowWidthAsLeading() };
}
if (result.action == InlineContentBreaker::Result::Action::WrapWithHyphen) {
ASSERT(result.isEndOfLine == InlineContentBreaker::IsEndOfLine::Yes);
ASSERT(m_line.trailingSoftHyphenWidth());
m_line.addTrailingHyphen(*m_line.trailingSoftHyphenWidth());
return { InlineContentBreaker::IsEndOfLine::Yes };
}
if (result.action == InlineContentBreaker::Result::Action::RevertToLastWrapOpportunity) {
ASSERT(result.isEndOfLine == InlineContentBreaker::IsEndOfLine::Yes);
ASSERT(!m_wrapOpportunityList.isEmpty());
return { InlineContentBreaker::IsEndOfLine::Yes, { rebuildLine(layoutRange, *m_wrapOpportunityList.last()), true } };
}
if (result.action == InlineContentBreaker::Result::Action::RevertToLastNonOverflowingWrapOpportunity) {
ASSERT(result.isEndOfLine == InlineContentBreaker::IsEndOfLine::Yes);
ASSERT(!m_wrapOpportunityList.isEmpty());
return { InlineContentBreaker::IsEndOfLine::Yes, { rebuildLineForTrailingSoftHyphen(layoutRange), true } };
}
if (result.action == InlineContentBreaker::Result::Action::Break) {
ASSERT(result.isEndOfLine == InlineContentBreaker::IsEndOfLine::Yes);
ASSERT(result.partialTrailingContent);
commitPartialContent(candidateRuns, *result.partialTrailingContent);
auto trailingRunIndex = result.partialTrailingContent->trailingRunIndex;
auto committedInlineItemCount = trailingRunIndex + 1;
if (!result.partialTrailingContent->partialRun)
return { InlineContentBreaker::IsEndOfLine::Yes, { committedInlineItemCount, false } };
auto partialRun = *result.partialTrailingContent->partialRun;
auto& trailingInlineTextItem = downcast<InlineTextItem>(candidateRuns[trailingRunIndex].inlineItem);
ASSERT(partialRun.length < trailingInlineTextItem.length());
auto overflowLength = trailingInlineTextItem.length() - partialRun.length;
return { InlineContentBreaker::IsEndOfLine::Yes, { committedInlineItemCount, false }, overflowLength, eligibleOverflowWidthAsLeading() };
}
ASSERT_NOT_REACHED();
return { InlineContentBreaker::IsEndOfLine::No };
}
void LineBuilder::commitPartialContent(const InlineContentBreaker::ContinuousContent::RunList& runs, const InlineContentBreaker::Result::PartialTrailingContent& partialTrailingContent)
{
for (size_t index = 0; index < runs.size(); ++index) {
auto& run = runs[index];
if (partialTrailingContent.trailingRunIndex == index) {
if (auto partialRun = partialTrailingContent.partialRun) {
ASSERT(run.inlineItem.isText());
auto& trailingInlineTextItem = downcast<InlineTextItem>(runs[partialTrailingContent.trailingRunIndex].inlineItem);
auto partialTrailingTextItem = trailingInlineTextItem.left(partialRun->length);
m_line.append(partialTrailingTextItem, partialRun->logicalWidth);
if (auto hyphenWidth = partialRun->hyphenWidth)
m_line.addTrailingHyphen(*hyphenWidth);
return;
}
m_line.append(run.inlineItem, run.logicalWidth);
return;
}
m_line.append(run.inlineItem, run.logicalWidth);
}
}
size_t LineBuilder::rebuildLine(const InlineItemRange& layoutRange, const InlineItem& lastInlineItemToAdd)
{
ASSERT(!m_wrapOpportunityList.isEmpty());
m_line.initialize();
auto currentItemIndex = layoutRange.start;
if (m_partialLeadingTextItem) {
m_line.append(*m_partialLeadingTextItem, inlineItemWidth(*m_partialLeadingTextItem, { }));
if (&m_partialLeadingTextItem.value() == &lastInlineItemToAdd)
return 1;
++currentItemIndex;
}
for (; currentItemIndex < layoutRange.end; ++currentItemIndex) {
auto& inlineItem = m_inlineItems[currentItemIndex];
m_line.append(inlineItem, inlineItemWidth(inlineItem, m_line.contentLogicalRight()));
if (&inlineItem == &lastInlineItemToAdd)
return currentItemIndex - layoutRange.start + 1;
}
return layoutRange.size();
}
size_t LineBuilder::rebuildLineForTrailingSoftHyphen(const InlineItemRange& layoutRange)
{
ASSERT(!m_wrapOpportunityList.isEmpty());
for (auto i = m_wrapOpportunityList.size(); i-- > 1;) {
auto& softWrapOpportunityItem = *m_wrapOpportunityList[i];
auto committedCount = rebuildLine(layoutRange, softWrapOpportunityItem);
auto availableWidth = m_lineLogicalRect.width() - m_line.contentLogicalRight();
auto trailingSoftHyphenWidth = m_line.trailingSoftHyphenWidth();
if (!trailingSoftHyphenWidth || trailingSoftHyphenWidth <= availableWidth) {
if (trailingSoftHyphenWidth)
m_line.addTrailingHyphen(*trailingSoftHyphenWidth);
return committedCount;
}
}
auto committedCount = rebuildLine(layoutRange, *m_wrapOpportunityList.first());
if (auto trailingSoftHyphenWidth = m_line.trailingSoftHyphenWidth())
m_line.addTrailingHyphen(*trailingSoftHyphenWidth);
return committedCount;
}
bool LineBuilder::isLastLineWithInlineContent(const InlineItemRange& lineRange, size_t lastInlineItemIndex, bool hasPartialTrailingContent) const
{
if (hasPartialTrailingContent)
return false;
if (lineRange.end == lastInlineItemIndex)
return true;
for (auto i = lastInlineItemIndex; i--;) {
if (!m_inlineItems[i].isFloat())
return i == lineRange.end - 1;
}
ASSERT_NOT_REACHED();
return false;
}
const ContainerBox& LineBuilder::root() const
{
return formattingContext().root();
}
const LayoutState& LineBuilder::layoutState() const
{
return formattingContext().layoutState();
}
}
}
#endif