InlineLineBuilder.cpp [plain text]
#include "config.h"
#include "InlineLineBuilder.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "InlineFormattingContext.h"
#include "InlineSoftLineBreakItem.h"
#include "RuntimeEnabledFeatures.h"
#include "TextUtil.h"
#include <wtf/IsoMallocInlines.h>
namespace WebCore {
namespace Layout {
static inline bool isWhitespacePreserved(const RenderStyle& style)
{
auto whitespace = style.whiteSpace();
return whitespace == WhiteSpace::Pre || whitespace == WhiteSpace::PreWrap || whitespace == WhiteSpace::BreakSpaces;
}
struct HangingContent {
public:
void reset();
InlineLayoutUnit width() const { return m_width; }
bool isConditional() const { return m_isConditional; }
void setIsConditional() { m_isConditional = true; }
void expand(InlineLayoutUnit width) { m_width += width; }
private:
bool m_isConditional { false };
InlineLayoutUnit m_width { 0 };
};
void HangingContent::reset()
{
m_isConditional = false;
m_width = 0;
}
LineBuilder::LineBuilder(const InlineFormattingContext& inlineFormattingContext, Optional<TextAlignMode> horizontalAlignment, IntrinsicSizing intrinsicSizing)
: m_inlineFormattingContext(inlineFormattingContext)
, m_trimmableTrailingContent(m_runs)
, m_horizontalAlignment(horizontalAlignment)
, m_isIntrinsicSizing(intrinsicSizing == IntrinsicSizing::Yes)
, m_shouldIgnoreTrailingLetterSpacing(RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled())
{
}
LineBuilder::~LineBuilder()
{
}
void LineBuilder::initialize(const Constraints& constraints)
{
ASSERT(m_isIntrinsicSizing || constraints.heightAndBaseline);
InlineLayoutUnit initialLineHeight = 0;
InlineLayoutUnit initialBaselineOffset = 0;
if (constraints.heightAndBaseline) {
m_initialStrut = constraints.heightAndBaseline->strut;
initialLineHeight = constraints.heightAndBaseline->height;
initialBaselineOffset = constraints.heightAndBaseline->baselineOffset;
} else
m_initialStrut = { };
auto lineRect = Display::InlineRect { constraints.logicalTopLeft, 0_lu, initialLineHeight };
auto baseline = LineBoxBuilder::Baseline { initialBaselineOffset, initialLineHeight - initialBaselineOffset };
m_lineBox = LineBoxBuilder { lineRect, baseline, initialBaselineOffset };
m_lineLogicalWidth = constraints.availableLogicalWidth;
m_hasIntrusiveFloat = constraints.lineIsConstrainedByFloat;
resetContent();
}
void LineBuilder::resetContent()
{
m_lineBox.setLogicalWidth({ });
m_lineBox.setIsConsideredEmpty();
m_runs.clear();
m_trimmableTrailingContent.reset();
m_lineIsVisuallyEmptyBeforeTrimmableTrailingContent = { };
}
LineBuilder::RunList LineBuilder::close(IsLastLineWithInlineContent isLastLineWithInlineContent)
{
removeTrailingTrimmableContent();
visuallyCollapsePreWrapOverflowContent();
auto hangingContent = collectHangingContent(isLastLineWithInlineContent);
if (!m_isIntrinsicSizing) {
unsigned inlineContainerNestingLevel = 0;
auto hasSeenTextOrLineBreak = false;
for (auto& run : m_runs) {
run.setLogicalHeight(runContentHeight(run));
inlineContainerNestingLevel = run.isContainerStart() ? inlineContainerNestingLevel + 1 : run.isContainerEnd() ? inlineContainerNestingLevel - 1 : inlineContainerNestingLevel;
auto runIsTextOrLineBreak = run.isText() || run.isLineBreak();
if (runIsTextOrLineBreak) {
if (hasSeenTextOrLineBreak)
continue;
hasSeenTextOrLineBreak = true;
if (!m_initialStrut)
continue;
if (inlineContainerNestingLevel)
continue;
}
auto& usedBaseline = runIsTextOrLineBreak ? *m_initialStrut : m_lineBox.baseline();
adjustBaselineAndLineHeight(run, usedBaseline);
}
if (isVisuallyEmpty()) {
m_lineBox.resetBaseline();
m_lineBox.setLogicalHeight({ });
}
if (formattingContext().quirks().lineDescentNeedsCollapsing(m_runs)) {
m_lineBox.shrinkVertically(m_lineBox.baseline().descent());
m_lineBox.resetDescent();
}
alignContentVertically();
alignHorizontally(hangingContent, isLastLineWithInlineContent);
}
return WTFMove(m_runs);
}
void LineBuilder::alignContentVertically()
{
ASSERT(!m_isIntrinsicSizing);
auto scrollableOverflowRect = m_lineBox.logicalRect();
for (auto& run : m_runs) {
InlineLayoutUnit logicalTop = 0;
auto& layoutBox = run.layoutBox();
auto verticalAlign = layoutBox.style().verticalAlign();
auto ascent = layoutBox.style().fontMetrics().ascent();
switch (verticalAlign) {
case VerticalAlign::Baseline:
if (run.isLineBreak() || run.isText())
logicalTop = baselineOffset() - ascent;
else if (run.isContainerStart()) {
auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
logicalTop = baselineOffset() - ascent - boxGeometry.borderTop() - boxGeometry.paddingTop().valueOr(0);
} else if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
auto& formattingState = layoutState().establishedInlineFormattingState(downcast<ContainerBox>(layoutBox));
auto inlineBlockBaselineOffset = formattingState.displayInlineContent()->lineBoxes.last().baselineOffset();
auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
auto baselineOffsetFromMarginBox = boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0) + inlineBlockBaselineOffset;
logicalTop = baselineOffset() - baselineOffsetFromMarginBox;
} else {
auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
logicalTop = baselineOffset() - (boxGeometry.verticalBorder() + boxGeometry.verticalPadding().valueOr(0_lu) + run.logicalRect().height() + boxGeometry.marginAfter());
}
break;
case VerticalAlign::Top:
logicalTop = 0_lu;
break;
case VerticalAlign::Bottom:
logicalTop = logicalBottom() - run.logicalRect().height();
break;
default:
ASSERT_NOT_IMPLEMENTED_YET();
break;
}
run.adjustLogicalTop(logicalTop);
scrollableOverflowRect.expandVerticallyToContain(run.logicalRect());
run.moveVertically(this->logicalTop());
run.moveHorizontally(this->logicalLeft());
}
m_lineBox.setScrollableOverflow(scrollableOverflowRect);
}
void LineBuilder::justifyRuns(InlineLayoutUnit availableWidth)
{
ASSERT(availableWidth > 0);
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 leftExpansion = lastRunWithContent->expansionBehavior() & LeftExpansionMask;
lastRunWithContent->setExpansionBehavior(leftExpansion | ForbidRightExpansion);
}
if (!expansionOpportunityCount)
return;
auto expansionToDistribute = availableWidth / expansionOpportunityCount;
InlineLayoutUnit accumulatedExpansion = 0;
for (auto& run : m_runs) {
if (!run.hasExpansionOpportunity()) {
run.moveHorizontally(accumulatedExpansion);
continue;
}
ASSERT(run.expansionOpportunityCount());
auto computedExpansion = expansionToDistribute * run.expansionOpportunityCount();
run.setComputedHorizontalExpansion(computedExpansion);
run.moveHorizontally(accumulatedExpansion);
accumulatedExpansion += computedExpansion;
}
}
void LineBuilder::alignHorizontally(const HangingContent& hangingContent, IsLastLineWithInlineContent isLastLine)
{
ASSERT(!m_isIntrinsicSizing);
auto availableWidth = this->availableWidth() + hangingContent.width();
if (m_runs.isEmpty() || availableWidth <= 0)
return;
auto computedHorizontalAlignment = [&] {
ASSERT(m_horizontalAlignment);
if (m_horizontalAlignment != TextAlignMode::Justify)
return *m_horizontalAlignment;
if (m_runs.last().isLineBreak() || isLastLine == IsLastLineWithInlineContent::Yes)
return TextAlignMode::Start;
return TextAlignMode::Justify;
}();
if (computedHorizontalAlignment == TextAlignMode::Justify) {
justifyRuns(availableWidth);
return;
}
auto adjustmentForAlignment = [] (auto horizontalAlignment, auto availableWidth) -> Optional<InlineLayoutUnit> {
switch (horizontalAlignment) {
case TextAlignMode::Left:
case TextAlignMode::WebKitLeft:
case TextAlignMode::Start:
return { };
case TextAlignMode::Right:
case TextAlignMode::WebKitRight:
case TextAlignMode::End:
return std::max<InlineLayoutUnit>(availableWidth, 0);
case TextAlignMode::Center:
case TextAlignMode::WebKitCenter:
return std::max<InlineLayoutUnit>(availableWidth / 2, 0);
case TextAlignMode::Justify:
ASSERT_NOT_REACHED();
break;
}
ASSERT_NOT_REACHED();
return { };
};
auto adjustment = adjustmentForAlignment(computedHorizontalAlignment, availableWidth);
if (!adjustment)
return;
m_lineBox.moveHorizontally(*adjustment);
for (auto& run : m_runs)
run.moveHorizontally(*adjustment);
}
void LineBuilder::removeTrailingTrimmableContent()
{
if (m_trimmableTrailingContent.isEmpty() || m_runs.isEmpty())
return;
if (RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled()) {
auto isTextAlignRight = [&] {
ASSERT(m_horizontalAlignment);
return m_horizontalAlignment == TextAlignMode::Right
|| m_horizontalAlignment == TextAlignMode::WebKitRight
|| m_horizontalAlignment == TextAlignMode::End;
}();
if (m_runs.last().isLineBreak() && availableWidth() >= 0 && !isTextAlignRight) {
m_trimmableTrailingContent.reset();
return;
}
}
m_lineBox.shrinkHorizontally(m_trimmableTrailingContent.remove());
if (m_lineIsVisuallyEmptyBeforeTrimmableTrailingContent) {
auto lineIsVisuallyEmpty = [&] {
for (auto& run : m_runs) {
if (isVisuallyNonEmpty(run))
return false;
}
return true;
};
if (lineIsVisuallyEmpty())
m_lineBox.setIsConsideredEmpty();
m_lineIsVisuallyEmptyBeforeTrimmableTrailingContent = { };
}
}
void LineBuilder::visuallyCollapsePreWrapOverflowContent()
{
ASSERT(m_trimmableTrailingContent.isEmpty());
auto overflowWidth = -availableWidth();
if (overflowWidth <= 0)
return;
InlineLayoutUnit trimmedContentWidth = 0;
for (auto& run : WTF::makeReversedRange(m_runs)) {
if (run.style().whiteSpace() != WhiteSpace::PreWrap) {
break;
}
auto preWrapVisuallyCollapsibleInlineItem = run.isContainerStart() || run.isContainerEnd() || 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_lineBox.shrinkHorizontally(trimmedContentWidth);
}
HangingContent LineBuilder::collectHangingContent(IsLastLineWithInlineContent isLastLineWithInlineContent)
{
auto hangingContent = HangingContent { };
ASSERT(m_trimmableTrailingContent.isEmpty());
if (isLastLineWithInlineContent == IsLastLineWithInlineContent::Yes)
hangingContent.setIsConditional();
for (auto& run : WTF::makeReversedRange(m_runs)) {
if (run.isContainerStart() || run.isContainerEnd())
continue;
if (run.isLineBreak()) {
hangingContent.setIsConditional();
continue;
}
if (!run.hasTrailingWhitespace())
break;
if (run.style().whiteSpace() != WhiteSpace::PreWrap)
break;
hangingContent.expand(run.trailingWhitespaceWidth());
}
return hangingContent;
}
void LineBuilder::moveLogicalLeft(InlineLayoutUnit delta)
{
if (!delta)
return;
ASSERT(delta > 0);
m_lineBox.moveHorizontally(delta);
m_lineLogicalWidth -= delta;
}
void LineBuilder::moveLogicalRight(InlineLayoutUnit delta)
{
ASSERT(delta > 0);
m_lineLogicalWidth -= delta;
}
void LineBuilder::append(const InlineItem& inlineItem, InlineLayoutUnit logicalWidth)
{
appendWith(inlineItem, { logicalWidth, false });
}
void LineBuilder::appendPartialTrailingTextItem(const InlineTextItem& inlineTextItem, InlineLayoutUnit logicalWidth, bool needsHyphen)
{
appendWith(inlineTextItem, { logicalWidth, needsHyphen });
}
void LineBuilder::appendWith(const InlineItem& inlineItem, const InlineRunDetails& inlineRunDetails)
{
if (inlineItem.isText())
appendTextContent(downcast<InlineTextItem>(inlineItem), inlineRunDetails.logicalWidth, inlineRunDetails.needsHyphen);
else if (inlineItem.isLineBreak())
appendLineBreak(inlineItem);
else if (inlineItem.isContainerStart())
appendInlineContainerStart(inlineItem, inlineRunDetails.logicalWidth);
else if (inlineItem.isContainerEnd())
appendInlineContainerEnd(inlineItem, inlineRunDetails.logicalWidth);
else if (inlineItem.layoutBox().isReplacedBox())
appendReplacedInlineBox(inlineItem, inlineRunDetails.logicalWidth);
else if (inlineItem.isBox())
appendNonReplacedInlineBox(inlineItem, inlineRunDetails.logicalWidth);
else
ASSERT_NOT_REACHED();
if (m_lineBox.isConsideredEmpty() && !m_runs.isEmpty() && isVisuallyNonEmpty(m_runs.last()))
m_lineBox.setIsConsideredNonEmpty();
}
void LineBuilder::appendNonBreakableSpace(const InlineItem& inlineItem, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth)
{
m_runs.append({ inlineItem, logicalLeft, logicalWidth });
m_lineBox.expandHorizontally(logicalWidth);
}
void LineBuilder::appendInlineContainerStart(const InlineItem& inlineItem, InlineLayoutUnit logicalWidth)
{
appendNonBreakableSpace(inlineItem, contentLogicalWidth(), logicalWidth);
}
void LineBuilder::appendInlineContainerEnd(const InlineItem& inlineItem, InlineLayoutUnit logicalWidth)
{
auto removeTrailingLetterSpacing = [&] {
if (!m_trimmableTrailingContent.isTrailingRunPartiallyTrimmable())
return;
m_lineBox.shrinkHorizontally(m_trimmableTrailingContent.removePartiallyTrimmableContent());
};
removeTrailingLetterSpacing();
appendNonBreakableSpace(inlineItem, contentLogicalRight(), logicalWidth);
}
void LineBuilder::appendTextContent(const InlineTextItem& inlineTextItem, InlineLayoutUnit logicalWidth, bool needsHyphen)
{
auto willCollapseCompletely = [&] {
if (!inlineTextItem.isCollapsible())
return false;
if (inlineTextItem.isEmptyContent())
return true;
for (auto& run : WTF::makeReversedRange(m_runs)) {
if (run.isBox())
return false;
if (run.isText())
return run.hasCollapsibleTrailingWhitespace();
ASSERT(run.isContainerStart() || run.isContainerEnd());
}
return !isWhitespacePreserved(inlineTextItem.style());
};
if (willCollapseCompletely())
return;
auto inlineTextItemNeedsNewRun = true;
if (!m_runs.isEmpty()) {
auto& lastRun = m_runs.last();
inlineTextItemNeedsNewRun = lastRun.hasCollapsedTrailingWhitespace() || !lastRun.isText() || &lastRun.layoutBox() != &inlineTextItem.layoutBox();
if (!inlineTextItemNeedsNewRun) {
lastRun.expand(inlineTextItem, logicalWidth);
if (needsHyphen) {
ASSERT(!lastRun.textContent()->needsHyphen());
lastRun.setNeedsHyphen();
}
}
}
if (inlineTextItemNeedsNewRun)
m_runs.append({ inlineTextItem, contentLogicalWidth(), logicalWidth, needsHyphen });
m_lineBox.expandHorizontally(logicalWidth);
if (inlineTextItem.isWhitespace() && !TextUtil::shouldPreserveTrailingWhitespace(inlineTextItem.style())) {
m_trimmableTrailingContent.addFullyTrimmableContent(m_runs.size() - 1, logicalWidth);
if (m_trimmableTrailingContent.isEmpty())
m_lineIsVisuallyEmptyBeforeTrimmableTrailingContent = isVisuallyEmpty();
return;
}
m_trimmableTrailingContent.reset();
if (!m_shouldIgnoreTrailingLetterSpacing && !inlineTextItem.isWhitespace() && inlineTextItem.style().letterSpacing() > 0)
m_trimmableTrailingContent.addPartiallyTrimmableContent(m_runs.size() - 1, logicalWidth);
}
void LineBuilder::appendNonReplacedInlineBox(const InlineItem& inlineItem, InlineLayoutUnit logicalWidth)
{
auto& layoutBox = inlineItem.layoutBox();
auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
auto horizontalMargin = boxGeometry.horizontalMargin();
m_runs.append({ inlineItem, contentLogicalWidth() + horizontalMargin.start, logicalWidth });
m_lineBox.expandHorizontally(logicalWidth + horizontalMargin.start + horizontalMargin.end);
m_trimmableTrailingContent.reset();
}
void LineBuilder::appendReplacedInlineBox(const InlineItem& inlineItem, InlineLayoutUnit logicalWidth)
{
ASSERT(inlineItem.layoutBox().isReplacedBox());
appendNonReplacedInlineBox(inlineItem, logicalWidth);
}
void LineBuilder::appendLineBreak(const InlineItem& inlineItem)
{
if (inlineItem.isHardLineBreak())
return m_runs.append({ inlineItem, contentLogicalWidth(), 0_lu });
ASSERT(inlineItem.isSoftLineBreak());
m_runs.append({ downcast<InlineSoftLineBreakItem>(inlineItem), contentLogicalWidth() });
}
void LineBuilder::adjustBaselineAndLineHeight(const Run& run, const LineBoxBuilder::Baseline& usedBaseline)
{
if (run.isText() || run.isLineBreak()) {
m_lineBox.setAscentIfGreater(usedBaseline.ascent());
m_lineBox.setDescentIfGreater(usedBaseline.descent());
m_lineBox.setLogicalHeightIfGreater(usedBaseline.height());
return;
}
auto& layoutBox = run.layoutBox();
auto& style = layoutBox.style();
if (run.isContainerStart()) {
auto& fontMetrics = style.fontMetrics();
if (style.verticalAlign() == VerticalAlign::Baseline) {
auto halfLeading = halfLeadingMetrics(fontMetrics, style.computedLineHeight());
if (halfLeading.descent() > 0)
m_lineBox.setDescentIfGreater(halfLeading.descent());
if (halfLeading.ascent() > 0)
m_lineBox.setAscentIfGreater(halfLeading.ascent());
m_lineBox.setLogicalHeightIfGreater(usedBaseline.height());
} else
m_lineBox.setLogicalHeightIfGreater(fontMetrics.height());
return;
}
if (run.isContainerEnd()) {
return;
}
if (run.isBox()) {
auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
auto marginBoxHeight = boxGeometry.marginBoxHeight();
switch (style.verticalAlign()) {
case VerticalAlign::Baseline: {
if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
auto& formattingState = layoutState().establishedInlineFormattingState(downcast<ContainerBox>(layoutBox));
auto& lastLineBox = formattingState.displayInlineContent()->lineBoxes.last();
auto inlineBlockBaseline = lastLineBox.baseline();
auto beforeHeight = boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0);
m_lineBox.setAscentIfGreater(inlineBlockBaseline.ascent());
m_lineBox.setDescentIfGreater(inlineBlockBaseline.descent());
m_lineBox.setBaselineOffsetIfGreater(beforeHeight + lastLineBox.baselineOffset());
m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
} else {
m_lineBox.setAscentIfGreater(marginBoxHeight);
m_lineBox.setLogicalHeightIfGreater(marginBoxHeight + std::max<InlineLayoutUnit>(0, usedBaseline.descent()));
}
break;
}
case VerticalAlign::Top:
m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
break;
case VerticalAlign::Bottom: {
auto lineLogicalHeight = m_lineBox.logicalHeight();
if (marginBoxHeight > lineLogicalHeight) {
m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
m_lineBox.setBaselineOffsetIfGreater(m_lineBox.baselineOffset() + (marginBoxHeight - lineLogicalHeight));
}
break;
}
default:
ASSERT_NOT_IMPLEMENTED_YET();
break;
}
return;
}
ASSERT_NOT_REACHED();
}
InlineLayoutUnit LineBuilder::runContentHeight(const Run& run) const
{
ASSERT(!m_isIntrinsicSizing);
auto& fontMetrics = run.style().fontMetrics();
if (run.isText() || run.isLineBreak())
return fontMetrics.height();
if (run.isContainerStart() || run.isContainerEnd())
return fontMetrics.height();
auto& layoutBox = run.layoutBox();
auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
if (layoutBox.isReplacedBox() || layoutBox.isFloatingPositioned())
return boxGeometry.contentBoxHeight();
return boxGeometry.marginBoxHeight();
}
bool LineBuilder::isVisuallyNonEmpty(const Run& run) const
{
if (run.isText())
return true;
if (run.isLineBreak())
return true;
if (run.isContainerStart() || run.isContainerEnd()) {
if (!run.logicalWidth())
return false;
auto& boxGeometry = formattingContext().geometryForBox(run.layoutBox());
if (run.isContainerStart())
return boxGeometry.borderLeft() || (boxGeometry.paddingLeft() && boxGeometry.paddingLeft().value());
return boxGeometry.borderRight() || (boxGeometry.paddingRight() && boxGeometry.paddingRight().value());
}
if (run.isBox()) {
if (run.layoutBox().isReplacedBox())
return true;
ASSERT(run.layoutBox().isInlineBlockBox() || run.layoutBox().isInlineTableBox());
if (!run.logicalWidth())
return false;
if (m_isIntrinsicSizing || formattingContext().geometryForBox(run.layoutBox()).height())
return true;
return false;
}
ASSERT_NOT_REACHED();
return false;
}
LineBoxBuilder::Baseline LineBuilder::halfLeadingMetrics(const FontMetrics& fontMetrics, InlineLayoutUnit lineLogicalHeight)
{
auto ascent = fontMetrics.ascent();
auto descent = fontMetrics.descent();
auto halfLeading = (lineLogicalHeight - (ascent + descent)) / 2;
auto adjustedAscent = std::max<InlineLayoutUnit>(floorf(ascent + halfLeading), 0);
auto adjustedDescent = std::max<InlineLayoutUnit>(ceilf(descent + halfLeading), 0);
return { adjustedAscent, adjustedDescent };
}
LayoutState& LineBuilder::layoutState() const
{
return formattingContext().layoutState();
}
const InlineFormattingContext& LineBuilder::formattingContext() const
{
return m_inlineFormattingContext;
}
LineBuilder::TrimmableTrailingContent::TrimmableTrailingContent(RunList& runs)
: m_runs(runs)
{
}
void LineBuilder::TrimmableTrailingContent::addFullyTrimmableContent(size_t runIndex, InlineLayoutUnit trimmableWidth)
{
ASSERT(!m_hasFullyTrimmableContent);
m_fullyTrimmableWidth = trimmableWidth;
m_hasFullyTrimmableContent = true;
m_firstRunIndex = m_firstRunIndex.valueOr(runIndex);
}
void LineBuilder::TrimmableTrailingContent::addPartiallyTrimmableContent(size_t runIndex, InlineLayoutUnit trimmableWidth)
{
ASSERT(!m_firstRunIndex);
ASSERT(!m_hasFullyTrimmableContent);
ASSERT(!m_partiallyTrimmableWidth);
ASSERT(trimmableWidth);
m_partiallyTrimmableWidth = trimmableWidth;
m_firstRunIndex = runIndex;
}
InlineLayoutUnit LineBuilder::TrimmableTrailingContent::remove()
{
ASSERT(!isEmpty());
auto& trimmableRun = m_runs[*m_firstRunIndex];
ASSERT(trimmableRun.isText());
if (m_hasFullyTrimmableContent)
trimmableRun.removeTrailingWhitespace();
if (m_partiallyTrimmableWidth)
trimmableRun.removeTrailingLetterSpacing();
auto trimmableWidth = width();
for (auto index = *m_firstRunIndex + 1; index < m_runs.size(); ++index) {
auto& run = m_runs[index];
ASSERT(run.isContainerStart() || run.isContainerEnd() || run.isLineBreak());
run.moveHorizontally(-trimmableWidth);
}
reset();
return trimmableWidth;
}
InlineLayoutUnit LineBuilder::TrimmableTrailingContent::removePartiallyTrimmableContent()
{
ASSERT(!m_fullyTrimmableWidth);
return remove();
}
LineBuilder::Run::Run(const InlineItem& inlineItem, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth)
: m_type(inlineItem.type())
, m_layoutBox(&inlineItem.layoutBox())
, m_logicalRect({ 0, logicalLeft, logicalWidth, 0 })
{
}
LineBuilder::Run::Run(const InlineSoftLineBreakItem& softLineBreakItem, InlineLayoutUnit logicalLeft)
: m_type(softLineBreakItem.type())
, m_layoutBox(&softLineBreakItem.layoutBox())
, m_logicalRect({ 0, logicalLeft, 0, 0 })
, m_textContent({ softLineBreakItem.position(), 1, softLineBreakItem.inlineTextBox().content(), false })
{
}
LineBuilder::Run::Run(const InlineTextItem& inlineTextItem, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth, bool needsHyphen)
: m_type(InlineItem::Type::Text)
, m_layoutBox(&inlineTextItem.layoutBox())
, m_logicalRect({ 0, logicalLeft, logicalWidth, 0 })
, m_trailingWhitespaceType(trailingWhitespaceType(inlineTextItem))
, m_textContent({ inlineTextItem.start(), m_trailingWhitespaceType == TrailingWhitespace::Collapsed ? 1 : inlineTextItem.length(), inlineTextItem.inlineTextBox().content(), needsHyphen })
{
if (m_trailingWhitespaceType != TrailingWhitespace::None) {
m_trailingWhitespaceWidth = logicalWidth;
if (!isWhitespacePreserved(inlineTextItem.style()))
m_expansionOpportunityCount = 1;
}
}
void LineBuilder::Run::expand(const InlineTextItem& inlineTextItem, InlineLayoutUnit logicalWidth)
{
ASSERT(!hasCollapsedTrailingWhitespace());
ASSERT(isText() && inlineTextItem.isText());
ASSERT(m_layoutBox == &inlineTextItem.layoutBox());
m_logicalRect.expandHorizontally(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 (!isWhitespacePreserved(inlineTextItem.style()))
++m_expansionOpportunityCount;
setExpansionBehavior(DefaultExpansion);
m_textContent->expand(m_trailingWhitespaceType == TrailingWhitespace::Collapsed ? 1 : inlineTextItem.length());
}
bool LineBuilder::Run::hasTrailingLetterSpacing() const
{
if (RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled())
return false;
return !hasTrailingWhitespace() && style().letterSpacing() > 0;
}
InlineLayoutUnit LineBuilder::Run::trailingLetterSpacing() const
{
if (!hasTrailingLetterSpacing())
return 0_lu;
return InlineLayoutUnit { style().letterSpacing() };
}
void LineBuilder::Run::removeTrailingLetterSpacing()
{
ASSERT(hasTrailingLetterSpacing());
shrinkHorizontally(trailingLetterSpacing());
ASSERT(logicalWidth() > 0 || (!logicalWidth() && style().letterSpacing() >= intMaxForLayoutUnit));
}
void LineBuilder::Run::removeTrailingWhitespace()
{
ASSERT(m_textContent->length());
constexpr size_t trailingTrimmableContentLength = 1;
m_textContent->shrink(trailingTrimmableContentLength);
visuallyCollapseTrailingWhitespace();
}
void LineBuilder::Run::visuallyCollapseTrailingWhitespace()
{
shrinkHorizontally(m_trailingWhitespaceWidth);
m_trailingWhitespaceWidth = { };
m_trailingWhitespaceType = TrailingWhitespace::None;
if (!isWhitespacePreserved(style())) {
ASSERT(m_expansionOpportunityCount);
m_expansionOpportunityCount--;
}
setExpansionBehavior(AllowLeftExpansion | AllowRightExpansion);
}
void LineBuilder::Run::setExpansionBehavior(ExpansionBehavior expansionBehavior)
{
ASSERT(isText());
m_expansion.behavior = expansionBehavior;
}
inline ExpansionBehavior LineBuilder::Run::expansionBehavior() const
{
ASSERT(isText());
return m_expansion.behavior;
}
void LineBuilder::Run::setComputedHorizontalExpansion(InlineLayoutUnit logicalExpansion)
{
ASSERT(isText());
ASSERT(hasExpansionOpportunity());
m_logicalRect.expandHorizontally(logicalExpansion);
m_expansion.horizontalExpansion = logicalExpansion;
}
}
}
#endif