LineLayoutContext.cpp [plain text]
#include "config.h"
#include "LineLayoutContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "LayoutBox.h"
#include "TextUtil.h"
namespace WebCore {
namespace Layout {
static bool endsWithSoftWrapOpportunity(const InlineTextItem& currentTextItem, const InlineTextItem& nextInlineTextItem)
{
ASSERT(!nextInlineTextItem.isWhitespace());
if (currentTextItem.isWhitespace())
return true;
if (¤tTextItem.layoutBox() == &nextInlineTextItem.layoutBox())
return true;
auto previousContent = currentTextItem.layoutBox().textContext()->content;
auto lineBreakIterator = LazyLineBreakIterator { nextInlineTextItem.layoutBox().textContext()->content };
auto previousContentLength = previousContent.length();
UChar lastCharacter = previousContentLength ? previousContent[previousContentLength - 1] : 0;
UChar secondToLastCharacter = previousContentLength > 1 ? previousContent[previousContentLength - 2] : 0;
lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
return !TextUtil::findNextBreakablePosition(lineBreakIterator, 0, nextInlineTextItem.style());
}
static bool isAtSoftWrapOpportunity(const InlineItem& current, const InlineItem& next)
{
ASSERT(current.isText() || current.isBox());
ASSERT(next.isText() || next.isBox());
if (current.isBox() || next.isBox()) {
return true;
}
if (current.style().lineBreak() == LineBreak::Anywhere || next.style().lineBreak() == LineBreak::Anywhere) {
return true;
}
auto& currentInlineTextItem = downcast<InlineTextItem>(current);
auto& nextInlineTextItem = downcast<InlineTextItem>(next);
if (currentInlineTextItem.isWhitespace()) {
return true;
}
if (nextInlineTextItem.isWhitespace()) {
return nextInlineTextItem.style().whiteSpace() != WhiteSpace::BreakSpaces;
}
return endsWithSoftWrapOpportunity(currentInlineTextItem, nextInlineTextItem);
}
static size_t nextWrapOpportunity(const InlineItems& inlineContent, unsigned startIndex)
{
auto end = inlineContent.size();
struct WrapContent {
WrapContent(size_t index, bool isAtLineBreak)
: m_index(index)
, m_isAtLineBreak(isAtLineBreak)
{
}
size_t operator*() const { return m_index; }
bool isAtLineBreak() const { return m_isAtLineBreak; }
private:
size_t m_index { 0 };
bool m_isAtLineBreak { false };
};
auto nextInlineItemWithContent = [&] (auto index) {
for (; index < end; ++index) {
auto& inlineItem = *inlineContent[index];
if (inlineItem.isText() || inlineItem.isBox() || inlineItem.isLineBreak())
return WrapContent { index, inlineItem.isLineBreak() };
}
return WrapContent { end, false };
};
auto startContent = nextInlineItemWithContent(startIndex);
if (startContent.isAtLineBreak()) {
return *startContent + 1;
}
while (*startContent != end) {
auto nextContent = nextInlineItemWithContent(*startContent + 1);
if (*nextContent == end)
return *nextContent;
if (nextContent.isAtLineBreak()) {
return *nextContent + 1;
}
if (isAtSoftWrapOpportunity(*inlineContent[*startContent], *inlineContent[*nextContent])) {
auto candidateIndex = *startContent + 1;
for (; candidateIndex < *nextContent; ++candidateIndex) {
if (inlineContent[candidateIndex]->isContainerStart()) {
return candidateIndex;
}
}
return candidateIndex;
}
startContent = nextContent;
}
return end;
}
struct LineCandidateContent {
void append(const InlineItem&, Optional<InlineLayoutUnit> logicalWidth = WTF::nullopt);
bool hasIntrusiveFloats() const { return !m_floats.isEmpty(); }
const LineBreaker::RunList& inlineRuns() const { return m_inlineRuns; }
const LineLayoutContext::FloatList& floats() const { return m_floats; }
const InlineItem* trailingLineBreak() const { return m_trailingLineBreak; }
private:
void setTrailingLineBreak(const InlineItem& lineBreakItem) { m_trailingLineBreak = &lineBreakItem; }
LineBreaker::RunList m_inlineRuns;
LineLayoutContext::FloatList m_floats;
const InlineItem* m_trailingLineBreak { nullptr };
};
void LineCandidateContent::append(const InlineItem& inlineItem, Optional<InlineLayoutUnit> logicalWidth)
{
ASSERT(!trailingLineBreak());
if (inlineItem.isLineBreak())
return setTrailingLineBreak(inlineItem);
if (inlineItem.isFloat())
return m_floats.append(makeWeakPtr(inlineItem));
m_inlineRuns.append({ inlineItem, *logicalWidth });
}
static InlineLayoutUnit inlineItemWidth(const FormattingContext& formattingContext, const InlineItem& inlineItem, InlineLayoutUnit contentLogicalLeft)
{
if (inlineItem.isLineBreak())
return 0;
if (is<InlineTextItem>(inlineItem)) {
auto& inlineTextItem = downcast<InlineTextItem>(inlineItem);
if (auto contentWidth = inlineTextItem.width())
return *contentWidth;
auto end = inlineTextItem.isCollapsible() ? inlineTextItem.start() + 1 : inlineTextItem.end();
return TextUtil::width(inlineTextItem, inlineTextItem.start(), end, contentLogicalLeft);
}
auto& layoutBox = inlineItem.layoutBox();
auto& boxGeometry = formattingContext.geometryForBox(layoutBox);
if (layoutBox.isFloatingPositioned())
return boxGeometry.marginBoxWidth();
if (layoutBox.replaced())
return boxGeometry.width();
if (inlineItem.isContainerStart())
return boxGeometry.marginStart() + boxGeometry.borderLeft() + boxGeometry.paddingLeft().valueOr(0);
if (inlineItem.isContainerEnd())
return boxGeometry.marginEnd() + boxGeometry.borderRight() + boxGeometry.paddingRight().valueOr(0);
return boxGeometry.width();
}
static inline bool isLineConsideredEmpty(const LineBuilder& line)
{
return line.isVisuallyEmpty() && !line.hasIntrusiveFloat();
}
LineLayoutContext::LineLayoutContext(const InlineFormattingContext& inlineFormattingContext, const Container& formattingContextRoot, const InlineItems& inlineItems)
: m_inlineFormattingContext(inlineFormattingContext)
, m_formattingContextRoot(formattingContextRoot)
, m_inlineItems(inlineItems)
{
}
LineLayoutContext::LineContent LineLayoutContext::layoutLine(LineBuilder& line, unsigned leadingInlineItemIndex, Optional<unsigned> partialLeadingContentLength)
{
auto reset = [&] {
ASSERT(m_floats.isEmpty());
m_partialTrailingTextItem = { };
m_partialLeadingTextItem = { };
};
reset();
auto lineBreaker = LineBreaker { };
auto currentItemIndex = leadingInlineItemIndex;
unsigned committedInlineItemCount = 0;
while (currentItemIndex < m_inlineItems.size()) {
auto candidateContent = nextContentForLine(currentItemIndex, partialLeadingContentLength, line.lineBox().logicalWidth());
if (candidateContent.hasIntrusiveFloats()) {
auto result = tryAddingFloatItems(line, candidateContent.floats());
committedInlineItemCount += result.committedCount;
if (result.isEndOfLine == LineBreaker::IsEndOfLine::Yes) {
return close(line, leadingInlineItemIndex, committedInlineItemCount, { });
}
}
if (!candidateContent.inlineRuns().isEmpty()) {
auto result = tryAddingInlineItems(lineBreaker, line, candidateContent);
if (result.revertTo) {
ASSERT(!result.committedCount);
ASSERT(result.isEndOfLine == LineBreaker::IsEndOfLine::Yes);
committedInlineItemCount -= line.revert(*result.revertTo);
}
committedInlineItemCount += result.committedCount;
if (result.isEndOfLine == LineBreaker::IsEndOfLine::Yes) {
return close(line, leadingInlineItemIndex, committedInlineItemCount, result.partialContent);
}
} else if (auto* trailingLineBreak = candidateContent.trailingLineBreak()) {
line.append(*trailingLineBreak, 0);
return close(line, leadingInlineItemIndex, ++committedInlineItemCount, { });
}
currentItemIndex = leadingInlineItemIndex + committedInlineItemCount;
partialLeadingContentLength = { };
}
return close(line, leadingInlineItemIndex, committedInlineItemCount, { });
}
LineLayoutContext::LineContent LineLayoutContext::close(LineBuilder& line, unsigned leadingInlineItemIndex, unsigned committedInlineItemCount, Optional<LineContent::PartialContent> partialContent)
{
ASSERT(committedInlineItemCount || line.hasIntrusiveFloat());
if (!committedInlineItemCount)
return LineContent { { }, { }, WTFMove(m_floats), line.close(), line.lineBox() };
if (partialContent && partialContent->trailingContentNeedsHyphen)
++m_successiveHyphenatedLineCount;
else
m_successiveHyphenatedLineCount = 0;
auto trailingInlineItemIndex = leadingInlineItemIndex + committedInlineItemCount - 1;
auto isLastLineWithInlineContent = [&] {
if (trailingInlineItemIndex == m_inlineItems.size() - 1)
return LineBuilder::IsLastLineWithInlineContent::Yes;
if (partialContent)
return LineBuilder::IsLastLineWithInlineContent::No;
for (auto i = m_inlineItems.size(); i--;) {
if (!m_inlineItems[i]->isFloat())
return i == trailingInlineItemIndex ? LineBuilder::IsLastLineWithInlineContent::Yes : LineBuilder::IsLastLineWithInlineContent::No;
}
ASSERT_NOT_REACHED();
return LineBuilder::IsLastLineWithInlineContent::No;
}();
return LineContent { trailingInlineItemIndex, partialContent, WTFMove(m_floats), line.close(isLastLineWithInlineContent), line.lineBox() };
}
LineCandidateContent LineLayoutContext::nextContentForLine(unsigned inlineItemIndex, Optional<unsigned> partialLeadingContentLength, InlineLayoutUnit currentLogicalRight)
{
ASSERT(inlineItemIndex < m_inlineItems.size());
auto softWrapOpportunityIndex = nextWrapOpportunity(m_inlineItems, inlineItemIndex);
ASSERT(softWrapOpportunityIndex <= m_inlineItems.size());
auto candidateContent = LineCandidateContent { };
if (partialLeadingContentLength) {
m_partialLeadingTextItem = downcast<InlineTextItem>(*m_inlineItems[inlineItemIndex]).right(*partialLeadingContentLength);
auto itemWidth = inlineItemWidth(formattingContext(), *m_partialLeadingTextItem, currentLogicalRight);
candidateContent.append(*m_partialLeadingTextItem, itemWidth);
currentLogicalRight += itemWidth;
++inlineItemIndex;
}
for (auto index = inlineItemIndex; index < softWrapOpportunityIndex; ++index) {
auto& inlineItem = *m_inlineItems[index];
if (inlineItem.isFloat()) {
candidateContent.append(inlineItem);
continue;
}
auto inlineItenmWidth = inlineItemWidth(formattingContext(), inlineItem, currentLogicalRight);
candidateContent.append(inlineItem, inlineItenmWidth);
currentLogicalRight += inlineItenmWidth;
}
return candidateContent;
}
LineLayoutContext::Result LineLayoutContext::tryAddingFloatItems(LineBuilder& line, const FloatList& floats)
{
size_t committedFloatItemCount = 0;
for (auto& floatItem : floats) {
auto logicalWidth = inlineItemWidth(formattingContext(), *floatItem, { });
auto lineIsConsideredEmpty = line.isVisuallyEmpty() && !line.hasIntrusiveFloat();
if (LineBreaker().shouldWrapFloatBox(logicalWidth, line.availableWidth() + line.trailingCollapsibleWidth(), lineIsConsideredEmpty))
return { LineBreaker::IsEndOfLine::Yes, committedFloatItemCount };
++committedFloatItemCount;
auto& floatBox = floatItem->layoutBox();
line.setHasIntrusiveFloat();
if (floatBox.isLeftFloatingPositioned())
line.moveLogicalLeft(logicalWidth);
else
line.moveLogicalRight(logicalWidth);
m_floats.append(floatItem);
}
return { LineBreaker::IsEndOfLine::No, committedFloatItemCount };
}
LineLayoutContext::Result LineLayoutContext::tryAddingInlineItems(LineBreaker& lineBreaker, LineBuilder& line, const LineCandidateContent& candidateContent)
{
auto shouldDisableHyphenation = [&] {
auto& style = root().style();
unsigned limitLines = style.hyphenationLimitLines() == RenderStyle::initialHyphenationLimitLines() ? std::numeric_limits<unsigned>::max() : style.hyphenationLimitLines();
return m_successiveHyphenatedLineCount >= limitLines;
};
auto lineStatus = LineBreaker::LineStatus { line.availableWidth(), line.trailingCollapsibleWidth(), line.isTrailingRunFullyCollapsible(), isLineConsideredEmpty(line) };
if (shouldDisableHyphenation())
lineBreaker.setHyphenationDisabled();
auto& candidateRuns = candidateContent.inlineRuns();
auto result = lineBreaker.shouldWrapInlineContent(candidateRuns, lineStatus);
if (result.action == LineBreaker::Result::Action::Keep) {
commitContent(line, candidateRuns, { });
if (auto* lineBreakItem = candidateContent.trailingLineBreak()) {
line.append(*lineBreakItem, 0);
return { LineBreaker::IsEndOfLine::Yes, candidateRuns.size() + 1 };
}
return { result.isEndOfLine, candidateRuns.size() };
}
if (result.action == LineBreaker::Result::Action::Push) {
return { result.isEndOfLine };
}
if (result.action == LineBreaker::Result::Action::Revert) {
return { result.isEndOfLine, 0, { }, result.revertTo };
}
if (result.action == LineBreaker::Result::Action::Split) {
ASSERT(result.partialTrailingContent);
commitContent(line, candidateRuns, result.partialTrailingContent);
auto trailingRunIndex = result.partialTrailingContent->trailingRunIndex;
auto committedInlineItemCount = trailingRunIndex + 1;
if (!result.partialTrailingContent->partialRun)
return { result.isEndOfLine, committedInlineItemCount };
auto partialRun = *result.partialTrailingContent->partialRun;
auto& trailingInlineTextItem = downcast<InlineTextItem>(candidateRuns[trailingRunIndex].inlineItem);
auto overflowLength = trailingInlineTextItem.length() - partialRun.length;
return { result.isEndOfLine, committedInlineItemCount, LineContent::PartialContent { partialRun.needsHyphen, overflowLength } };
}
ASSERT_NOT_REACHED();
return { LineBreaker::IsEndOfLine::No };
}
void LineLayoutContext::commitContent(LineBuilder& line, const LineBreaker::RunList& runs, Optional<LineBreaker::Result::PartialTrailingContent> partialTrailingContent)
{
for (size_t index = 0; index < runs.size(); ++index) {
auto& run = runs[index];
if (partialTrailingContent && partialTrailingContent->trailingRunIndex == index) {
ASSERT(run.inlineItem.isText());
if (auto partialRun = partialTrailingContent->partialRun) {
auto& trailingInlineTextItem = downcast<InlineTextItem>(runs[partialTrailingContent->trailingRunIndex].inlineItem);
ASSERT(!m_partialTrailingTextItem);
m_partialTrailingTextItem = trailingInlineTextItem.left(partialRun->length);
line.append(*m_partialTrailingTextItem, partialRun->logicalWidth);
return;
}
line.append(run.inlineItem, run.logicalWidth);
return;
}
line.append(run.inlineItem, run.logicalWidth);
}
}
}
}
#endif