#include "config.h"
#include "InlineLine.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "FontCascade.h"
#include "InlineFormattingContext.h"
#include "InlineSoftLineBreakItem.h"
#include "LayoutBoxGeometry.h"
#include "RuntimeEnabledFeatures.h"
#include "TextFlags.h"
#include "TextUtil.h"
#include <wtf/IsoMallocInlines.h>
namespace WebCore {
namespace Layout {
Line::Line(const InlineFormattingContext& inlineFormattingContext)
: m_inlineFormattingContext(inlineFormattingContext)
, m_trimmableTrailingContent(m_runs)
{
}
Line::~Line()
{
}
void Line::initialize()
{
m_contentLogicalWidth = { };
m_runs.clear();
m_trailingSoftHyphenWidth = { };
m_trimmableTrailingContent.reset();
m_isConsideredEmpty = true;
m_isConsideredEmptyBeforeTrimmableTrailingContent = { };
}
void Line::removeCollapsibleContent(InlineLayoutUnit extraHorizontalSpace)
{
removeTrailingTrimmableContent();
visuallyCollapsePreWrapOverflowContent(extraHorizontalSpace);
}
void Line::applyRunExpansion(InlineLayoutUnit extraHorizontalSpace)
{
ASSERT(formattingContext().root().style().textAlign() == TextAlignMode::Justify);
if (m_runs.isEmpty() || m_runs.last().isLineBreak())
return;
auto expansionOpportunityCount = 0;
Run* lastRunWithContent = nullptr;
for (auto& run : m_runs) {
expansionOpportunityCount += run.expansionOpportunityCount();
if (run.isText() || run.isBox())
lastRunWithContent = &run;
}
if (lastRunWithContent && lastRunWithContent->hasExpansionOpportunity()) {
auto leadingExpansion = lastRunWithContent->expansionBehavior() & LeftExpansionMask;
lastRunWithContent->setExpansionBehavior(leadingExpansion | ForbidRightExpansion);
}
if (!expansionOpportunityCount || !extraHorizontalSpace)
return;
auto expansionToDistribute = extraHorizontalSpace / expansionOpportunityCount;
auto accumulatedExpansion = InlineLayoutUnit { };
for (auto& run : m_runs) {
run.moveHorizontally(accumulatedExpansion);
if (!run.hasExpansionOpportunity())
continue;
ASSERT(run.expansionOpportunityCount());
auto computedExpansion = expansionToDistribute * run.expansionOpportunityCount();
run.setHorizontalExpansion(computedExpansion);
run.shrinkHorizontally(-computedExpansion);
accumulatedExpansion += computedExpansion;
}
m_contentLogicalWidth += accumulatedExpansion;
}
void Line::removeTrailingTrimmableContent()
{
if (m_trimmableTrailingContent.isEmpty() || m_runs.isEmpty())
return;
if (RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled()) {
auto isTextAlignRight = [&] {
auto textAlign = formattingContext().root().style().textAlign();
return textAlign == TextAlignMode::Right
|| textAlign == TextAlignMode::WebKitRight
|| textAlign == TextAlignMode::End;
}();
if (m_runs.last().isLineBreak() && !isTextAlignRight) {
m_trimmableTrailingContent.reset();
return;
}
}
m_contentLogicalWidth -= m_trimmableTrailingContent.remove();
if (m_isConsideredEmptyBeforeTrimmableTrailingContent) {
m_isConsideredEmpty = [&] {
for (auto& run : m_runs) {
if (!isRunConsideredEmpty(run))
return false;
}
return true;
}();
m_isConsideredEmptyBeforeTrimmableTrailingContent = { };
}
}
void Line::visuallyCollapsePreWrapOverflowContent(InlineLayoutUnit extraHorizontalSpace)
{
ASSERT(m_trimmableTrailingContent.isEmpty());
auto overflowWidth = -extraHorizontalSpace;
if (overflowWidth <= 0)
return;
InlineLayoutUnit trimmedContentWidth = 0;
for (auto& run : WTF::makeReversedRange(m_runs)) {
if (run.style().whiteSpace() != WhiteSpace::PreWrap) {
break;
}
auto preWrapVisuallyCollapsibleInlineItem = run.isInlineBoxStart() || run.isInlineBoxEnd() || run.hasTrailingWhitespace();
if (!preWrapVisuallyCollapsibleInlineItem)
break;
ASSERT(!run.hasCollapsibleTrailingWhitespace());
InlineLayoutUnit trimmableWidth = { };
if (run.isText()) {
trimmableWidth = run.trailingWhitespaceWidth();
run.visuallyCollapseTrailingWhitespace();
} else {
trimmableWidth = run.logicalWidth();
run.shrinkHorizontally(trimmableWidth);
}
trimmedContentWidth += trimmableWidth;
overflowWidth -= trimmableWidth;
if (overflowWidth <= 0)
break;
}
m_contentLogicalWidth -= trimmedContentWidth;
}
void Line::append(const InlineItem& inlineItem, InlineLayoutUnit logicalWidth)
{
if (inlineItem.isText())
appendTextContent(downcast<InlineTextItem>(inlineItem), logicalWidth);
else if (inlineItem.isLineBreak())
appendLineBreak(inlineItem);
else if (inlineItem.isWordBreakOpportunity())
appendWordBreakOpportunity(inlineItem);
else if (inlineItem.isInlineBoxStart())
appendInlineBoxStart(inlineItem, logicalWidth);
else if (inlineItem.isInlineBoxEnd())
appendInlineBoxEnd(inlineItem, logicalWidth);
else if (inlineItem.layoutBox().isReplacedBox())
appendReplacedInlineBox(inlineItem, logicalWidth);
else if (inlineItem.isBox())
appendNonReplacedInlineBox(inlineItem, logicalWidth);
else
ASSERT_NOT_REACHED();
if (m_isConsideredEmpty && !m_runs.isEmpty() && !isRunConsideredEmpty(m_runs.last()))
m_isConsideredEmpty = false;
}
void Line::appendNonBreakableSpace(const InlineItem& inlineItem, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth)
{
m_runs.append({ inlineItem, logicalLeft, logicalWidth });
m_contentLogicalWidth += logicalWidth;
}
void Line::appendInlineBoxStart(const InlineItem& inlineItem, InlineLayoutUnit logicalWidth)
{
auto& boxGeometry = formattingContext().geometryForBox(inlineItem.layoutBox());
auto adjustedRunStart = contentLogicalRight() + std::min(boxGeometry.marginStart(), 0_lu);
appendNonBreakableSpace(inlineItem, adjustedRunStart, logicalWidth);
}
void Line::appendInlineBoxEnd(const InlineItem& inlineItem, InlineLayoutUnit logicalWidth)
{
auto removeTrailingLetterSpacing = [&] {
if (!m_trimmableTrailingContent.isTrailingRunPartiallyTrimmable())
return;
m_contentLogicalWidth -= m_trimmableTrailingContent.removePartiallyTrimmableContent();
};
removeTrailingLetterSpacing();
appendNonBreakableSpace(inlineItem, contentLogicalWidth(), logicalWidth);
}
void Line::appendTextContent(const InlineTextItem& inlineTextItem, InlineLayoutUnit logicalWidth)
{
auto& style = inlineTextItem.style();
auto willCollapseCompletely = [&] {
if (inlineTextItem.isEmptyContent())
return true;
if (!inlineTextItem.isWhitespace())
return false;
if (InlineTextItem::shouldPreserveSpacesAndTabs(inlineTextItem))
return false;
for (auto& run : WTF::makeReversedRange(m_runs)) {
if (run.isBox())
return false;
if (run.isText())
return run.hasCollapsibleTrailingWhitespace();
ASSERT(run.isInlineBoxStart() || run.isInlineBoxEnd() || run.isWordBreakOpportunity());
}
return true;
};
if (willCollapseCompletely())
return;
auto needsNewRun = [&] {
if (m_runs.isEmpty())
return true;
auto& lastRun = m_runs.last();
if (&lastRun.layoutBox() != &inlineTextItem.layoutBox())
return true;
if (!lastRun.isText())
return true;
if (lastRun.hasCollapsedTrailingWhitespace())
return true;
if (inlineTextItem.isWordSeparator() && style.fontCascade().wordSpacing())
return true;
return false;
}();
auto oldContentLogicalWidth = contentLogicalWidth();
if (needsNewRun) {
auto runLogicalLeft = contentLogicalRight() + (inlineTextItem.isWordSeparator() ? style.fontCascade().wordSpacing() : 0.0f);
m_runs.append({ inlineTextItem, runLogicalLeft, logicalWidth });
m_contentLogicalWidth = std::max(oldContentLogicalWidth, runLogicalLeft + logicalWidth);
} else {
m_runs.last().expand(inlineTextItem, logicalWidth);
m_contentLogicalWidth += logicalWidth;
}
if (inlineTextItem.isWhitespace() && !InlineTextItem::shouldPreserveSpacesAndTabs(inlineTextItem)) {
m_trimmableTrailingContent.addFullyTrimmableContent(m_runs.size() - 1, contentLogicalWidth() - oldContentLogicalWidth);
if (m_trimmableTrailingContent.isEmpty())
m_isConsideredEmptyBeforeTrimmableTrailingContent = isConsideredEmpty();
return;
}
m_trimmableTrailingContent.reset();
if (!formattingContext().layoutState().shouldIgnoreTrailingLetterSpacing() && !inlineTextItem.isWhitespace() && style.letterSpacing() > 0)
m_trimmableTrailingContent.addPartiallyTrimmableContent(m_runs.size() - 1, style.letterSpacing());
m_trailingSoftHyphenWidth = inlineTextItem.hasTrailingSoftHyphen() ? makeOptional(style.fontCascade().width(TextRun { StringView { style.hyphenString() } })) : WTF::nullopt;
}
void Line::appendNonReplacedInlineBox(const InlineItem& inlineItem, InlineLayoutUnit marginBoxLogicalWidth)
{
m_trimmableTrailingContent.reset();
m_trailingSoftHyphenWidth = { };
m_contentLogicalWidth += marginBoxLogicalWidth;
auto marginStart = formattingContext().geometryForBox(inlineItem.layoutBox()).marginStart();
if (marginStart >= 0) {
m_runs.append({ inlineItem, contentLogicalRight(), marginBoxLogicalWidth });
return;
}
m_runs.append({ inlineItem, contentLogicalRight() + marginStart, marginBoxLogicalWidth - marginStart });
}
void Line::appendReplacedInlineBox(const InlineItem& inlineItem, InlineLayoutUnit marginBoxLogicalWidth)
{
ASSERT(inlineItem.layoutBox().isReplacedBox());
appendNonReplacedInlineBox(inlineItem, marginBoxLogicalWidth);
}
void Line::appendLineBreak(const InlineItem& inlineItem)
{
m_trailingSoftHyphenWidth = { };
if (inlineItem.isHardLineBreak())
return m_runs.append({ inlineItem, contentLogicalRight(), 0_lu });
ASSERT(inlineItem.isSoftLineBreak());
m_runs.append({ downcast<InlineSoftLineBreakItem>(inlineItem), contentLogicalRight() });
}
void Line::appendWordBreakOpportunity(const InlineItem& inlineItem)
{
m_runs.append({ inlineItem, contentLogicalRight(), 0_lu });
}
bool Line::isRunConsideredEmpty(const Run& run) const
{
if (run.isText())
return false;
if (run.isLineBreak())
return true;
if (run.isInlineBoxStart() || run.isInlineBoxEnd()) {
if (!run.logicalWidth())
return true;
auto& boxGeometry = formattingContext().geometryForBox(run.layoutBox());
auto hasBorderOrPadding = run.isInlineBoxStart() ? boxGeometry.borderLeft() || (boxGeometry.paddingLeft() && boxGeometry.paddingLeft().value())
: boxGeometry.borderRight() || (boxGeometry.paddingRight() && boxGeometry.paddingRight().value());
return !hasBorderOrPadding;
}
if (run.isBox()) {
if (run.layoutBox().isReplacedBox())
return false;
ASSERT(run.layoutBox().isInlineBlockBox() || run.layoutBox().isInlineTableBox());
return !run.logicalWidth();
}
if (run.isWordBreakOpportunity())
return true;
ASSERT_NOT_REACHED();
return true;
}
void Line::addTrailingHyphen(InlineLayoutUnit hyphenLogicalWidth)
{
for (auto& run : WTF::makeReversedRange(m_runs)) {
if (!run.isText())
continue;
run.setNeedsHyphen(hyphenLogicalWidth);
m_contentLogicalWidth += hyphenLogicalWidth;
return;
}
ASSERT_NOT_REACHED();
}
const InlineFormattingContext& Line::formattingContext() const
{
return m_inlineFormattingContext;
}
Line::TrimmableTrailingContent::TrimmableTrailingContent(RunList& runs)
: m_runs(runs)
{
}
void Line::TrimmableTrailingContent::addFullyTrimmableContent(size_t runIndex, InlineLayoutUnit trimmableWidth)
{
ASSERT(!m_hasFullyTrimmableContent);
m_fullyTrimmableWidth = trimmableWidth;
m_hasFullyTrimmableContent = true;
m_firstTrimmableRunIndex = m_firstTrimmableRunIndex.valueOr(runIndex);
}
void Line::TrimmableTrailingContent::addPartiallyTrimmableContent(size_t runIndex, InlineLayoutUnit trimmableWidth)
{
ASSERT(!m_firstTrimmableRunIndex);
ASSERT(!m_hasFullyTrimmableContent);
ASSERT(!m_partiallyTrimmableWidth);
ASSERT(trimmableWidth);
m_partiallyTrimmableWidth = trimmableWidth;
m_firstTrimmableRunIndex = runIndex;
}
InlineLayoutUnit Line::TrimmableTrailingContent::remove()
{
ASSERT(!isEmpty());
auto& trimmableRun = m_runs[*m_firstTrimmableRunIndex];
ASSERT(trimmableRun.isText());
if (m_hasFullyTrimmableContent)
trimmableRun.removeTrailingWhitespace();
if (m_partiallyTrimmableWidth)
trimmableRun.removeTrailingLetterSpacing();
auto trimmableWidth = width();
for (auto index = *m_firstTrimmableRunIndex + 1; index < m_runs.size(); ++index) {
auto& run = m_runs[index];
ASSERT(run.isWordBreakOpportunity() || run.isInlineBoxStart() || run.isInlineBoxEnd() || run.isLineBreak());
run.moveHorizontally(-trimmableWidth);
}
if (!trimmableRun.textContent()->length()) {
m_runs.remove(*m_firstTrimmableRunIndex);
}
reset();
return trimmableWidth;
}
InlineLayoutUnit Line::TrimmableTrailingContent::removePartiallyTrimmableContent()
{
ASSERT(!m_fullyTrimmableWidth);
return remove();
}
Line::Run::Run(const InlineItem& inlineItem, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth)
: m_type(inlineItem.type())
, m_layoutBox(&inlineItem.layoutBox())
, m_logicalLeft(logicalLeft)
, m_logicalWidth(logicalWidth)
{
}
Line::Run::Run(const InlineSoftLineBreakItem& softLineBreakItem, InlineLayoutUnit logicalLeft)
: m_type(softLineBreakItem.type())
, m_layoutBox(&softLineBreakItem.layoutBox())
, m_logicalLeft(logicalLeft)
, m_textContent({ softLineBreakItem.position(), 1, softLineBreakItem.inlineTextBox().content() })
{
}
Line::Run::Run(const InlineTextItem& inlineTextItem, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth)
: m_type(InlineItem::Type::Text)
, m_layoutBox(&inlineTextItem.layoutBox())
, m_logicalLeft(logicalLeft)
, m_logicalWidth(logicalWidth)
, m_whitespaceIsExpansionOpportunity(!TextUtil::shouldPreserveSpacesAndTabs(inlineTextItem.layoutBox()))
, m_trailingWhitespaceType(trailingWhitespaceType(inlineTextItem))
, m_textContent({ inlineTextItem.start(), m_trailingWhitespaceType == TrailingWhitespace::Collapsed ? 1 : inlineTextItem.length(), inlineTextItem.inlineTextBox().content() })
{
if (m_trailingWhitespaceType != TrailingWhitespace::None) {
m_trailingWhitespaceWidth = logicalWidth;
if (m_whitespaceIsExpansionOpportunity)
m_expansionOpportunityCount = 1;
}
}
void Line::Run::expand(const InlineTextItem& inlineTextItem, InlineLayoutUnit logicalWidth)
{
ASSERT(!hasCollapsedTrailingWhitespace());
ASSERT(isText() && inlineTextItem.isText());
ASSERT(m_layoutBox == &inlineTextItem.layoutBox());
m_logicalWidth += logicalWidth;
m_trailingWhitespaceType = trailingWhitespaceType(inlineTextItem);
if (m_trailingWhitespaceType == TrailingWhitespace::None) {
m_trailingWhitespaceWidth = { };
setExpansionBehavior(AllowLeftExpansion | AllowRightExpansion);
m_textContent->expand(inlineTextItem.length());
return;
}
m_trailingWhitespaceWidth += logicalWidth;
if (m_whitespaceIsExpansionOpportunity)
++m_expansionOpportunityCount;
setExpansionBehavior(DefaultExpansion);
m_textContent->expand(m_trailingWhitespaceType == TrailingWhitespace::Collapsed ? 1 : inlineTextItem.length());
}
bool Line::Run::hasTrailingLetterSpacing() const
{
return !hasTrailingWhitespace() && style().letterSpacing() > 0;
}
InlineLayoutUnit Line::Run::trailingLetterSpacing() const
{
if (!hasTrailingLetterSpacing())
return { };
return InlineLayoutUnit { style().letterSpacing() };
}
void Line::Run::removeTrailingLetterSpacing()
{
ASSERT(hasTrailingLetterSpacing());
shrinkHorizontally(trailingLetterSpacing());
ASSERT(logicalWidth() > 0 || (!logicalWidth() && style().letterSpacing() >= intMaxForLayoutUnit));
}
void Line::Run::removeTrailingWhitespace()
{
ASSERT(m_textContent->length());
constexpr size_t trailingTrimmableContentLength = 1;
m_textContent->shrink(trailingTrimmableContentLength);
visuallyCollapseTrailingWhitespace();
}
void Line::Run::visuallyCollapseTrailingWhitespace()
{
shrinkHorizontally(m_trailingWhitespaceWidth);
m_trailingWhitespaceWidth = { };
m_trailingWhitespaceType = TrailingWhitespace::None;
if (m_whitespaceIsExpansionOpportunity) {
ASSERT(m_expansionOpportunityCount);
m_expansionOpportunityCount--;
}
setExpansionBehavior(AllowLeftExpansion | AllowRightExpansion);
}
void Line::Run::setExpansionBehavior(ExpansionBehavior expansionBehavior)
{
ASSERT(isText());
m_expansion.behavior = expansionBehavior;
}
ExpansionBehavior Line::Run::expansionBehavior() const
{
ASSERT(isText());
return m_expansion.behavior;
}
void Line::Run::setHorizontalExpansion(InlineLayoutUnit logicalExpansion)
{
ASSERT(isText());
ASSERT(hasExpansionOpportunity());
m_expansion.horizontalExpansion = logicalExpansion;
}
}
}
#endif