Skip to content

Commit

Permalink
[IFC][Ruby] Introduce LineBuilder::handleRubyContent
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=261626

Reviewed by Antti Koivisto.

Ruby _base_ content is supposed to be placed on the current line as if it was regular inline content.
In order to place inline content on the line we need logical width information (line breaking).
Ruby _base_ content logical width has dependency on paired annotation(s) (see ruby columns).

The idea here is that when IFC sees a <ruby>, we construct Ruby Formatting Context (RFC) which
 - takes an inline item range (<ruby> range)
 - runs ruby inline axis layout -> computes base run logical widths using ruby column sizing
 - probes line breaking
 - places content on the current line

as far as line building is concerned.

* Source/WebCore/layout/formattingContexts/inline/InlineFormattingGeometry.cpp:
(WebCore::Layout::InlineFormattingGeometry::nextWrapOpportunity):
* Source/WebCore/layout/formattingContexts/inline/InlineLineBuilder.cpp:
(WebCore::Layout::LineCandidate::InlineContent::rubyContainerRange const):
(WebCore::Layout::LineCandidate::InlineContent::appendRubyContainerRange):
(WebCore::Layout::LineCandidate::InlineContent::isRuby const):
(WebCore::Layout::LineCandidate::InlineContent::reset):
(WebCore::Layout::LineBuilder::candidateContentForLine):
(WebCore::Layout::LineBuilder::handleInlineContent):
(WebCore::Layout::LineBuilder::handleRubyContent):
* Source/WebCore/layout/formattingContexts/inline/InlineLineBuilder.h:
* Source/WebCore/layout/layouttree/LayoutBox.h:
(WebCore::Layout::Box::isRuby const):

Canonical link: https://commits.webkit.org/268065@main
  • Loading branch information
alanbaradlay committed Sep 17, 2023
1 parent 7d72155 commit ab6f97a
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,10 @@ size_t InlineFormattingGeometry::nextWrapOpportunity(size_t startIndex, const In
return index;
}
if (currentItem.isInlineBoxStart() || currentItem.isInlineBoxEnd()) {
if (currentItem.layoutBox().isRuby()) {
// Should be able to break _before_ <ruby>.
return index;
}
// Need to see what comes next to decide.
continue;
}
Expand Down Expand Up @@ -511,7 +515,7 @@ size_t InlineFormattingGeometry::nextWrapOpportunity(size_t startIndex, const In
// Soft wrap opportunity is at the first inline box that encloses the trailing content.
for (auto candidateIndex = start + 1; candidateIndex < end; ++candidateIndex) {
auto& inlineItem = inlineItems[candidateIndex];
ASSERT(inlineItem.isInlineBoxStart() || inlineItem.isInlineBoxEnd() || inlineItem.isOpaque());
ASSERT((inlineItem.isInlineBoxStart() || inlineItem.isInlineBoxEnd() || inlineItem.isOpaque()) && !inlineItem.layoutBox().isRuby());
if (inlineItem.isInlineBoxStart())
inlineBoxStack.append({ &inlineItem.layoutBox(), candidateIndex });
else if (inlineItem.isInlineBoxEnd() && !inlineBoxStack.isEmpty())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,17 @@ struct LineCandidate {

struct InlineContent {
const InlineContentBreaker::ContinuousContent& continuousContent() const { return m_continuousContent; }
const InlineItemRange& rubyContainerRange() const { return m_rubyContainerRange; }
const InlineItem* trailingLineBreak() const { return m_trailingLineBreak; }
const InlineItem* trailingWordBreakOpportunity() const { return m_trailingWordBreakOpportunity; }

void appendInlineItem(const InlineItem&, const RenderStyle&, InlineLayoutUnit logicalWidth);
void appendTrailingLineBreak(const InlineItem& lineBreakItem) { m_trailingLineBreak = &lineBreakItem; }
void appendtrailingWordBreakOpportunity(const InlineItem& wordBreakItem) { m_trailingWordBreakOpportunity = &wordBreakItem; }
void appendRubyContainerRange(const InlineItemRange& rubyRange) { m_rubyContainerRange = rubyRange; }
void reset();
bool isEmpty() const { return m_continuousContent.runs().isEmpty() && !trailingWordBreakOpportunity() && !trailingLineBreak(); }
bool isRuby() const { return !m_rubyContainerRange.isEmpty(); }

void setHasTrailingSoftWrapOpportunity(bool hasTrailingSoftWrapOpportunity) { m_hasTrailingSoftWrapOpportunity = hasTrailingSoftWrapOpportunity; }
bool hasTrailingSoftWrapOpportunity() const { return m_hasTrailingSoftWrapOpportunity; }
Expand All @@ -180,6 +183,7 @@ struct LineCandidate {

private:
InlineContentBreaker::ContinuousContent m_continuousContent;
InlineItemRange m_rubyContainerRange { };
const InlineItem* m_trailingLineBreak { nullptr };
const InlineItem* m_trailingWordBreakOpportunity { nullptr };
InlineLayoutUnit m_accumulatedClonedDecorationEnd { 0.f };
Expand Down Expand Up @@ -210,6 +214,7 @@ inline void LineCandidate::InlineContent::reset()
m_trailingLineBreak = { };
m_trailingWordBreakOpportunity = { };
m_accumulatedClonedDecorationEnd = { };
m_rubyContainerRange = { };
}

inline void LineCandidate::reset()
Expand Down Expand Up @@ -597,8 +602,8 @@ void LineBuilder::candidateContentForLine(LineCandidate& lineCandidate, size_t c
// softWrapOpportunityIndex == layoutRange.end means we don't have any wrap opportunity in this content.
ASSERT(softWrapOpportunityIndex <= layoutRange.endIndex());

auto isLineStart = currentInlineItemIndex == layoutRange.startIndex();
if (isLineStart && m_partialLeadingTextItem) {
auto isLeadingPartiaContent = currentInlineItemIndex == layoutRange.startIndex() && m_partialLeadingTextItem;
if (isLeadingPartiaContent) {
ASSERT(!m_overflowingLogicalWidth);
// Handle leading partial content first (overflowing text from the previous line).
auto itemWidth = formattingContext().formattingGeometry().inlineItemWidth(*m_partialLeadingTextItem, currentLogicalRight, isFirstFormattedLine());
Expand All @@ -607,6 +612,34 @@ void LineBuilder::candidateContentForLine(LineCandidate& lineCandidate, size_t c
++currentInlineItemIndex;
}

auto appendRubyContainerIfApplicable = [&] {
if (softWrapOpportunityIndex > currentInlineItemIndex || isLeadingPartiaContent)
return false;
// We must be at a ruby container and we should let the ruby formatting context handle the rest of the ruby content.
ASSERT(m_inlineItems[currentInlineItemIndex].layoutBox().isRuby());
// Ruby base content is handled as one atomic line candidate where ruby formatting context takes care of the internal details.
// Note that ruby content is placed on the line as regualar inline content by the ruby formatting context.
size_t nestingLevel = 1;
for (size_t index = softWrapOpportunityIndex + 1; index < layoutRange.endIndex(); ++index) {
auto& inlineItem = m_inlineItems[index];
if (!inlineItem.layoutBox().isRuby())
continue;
ASSERT(inlineItem.isInlineBoxStart() || inlineItem.isInlineBoxEnd());
nestingLevel += inlineItem.isInlineBoxEnd() ? -1 : 1;
if (!nestingLevel) {
ASSERT(&m_inlineItems[softWrapOpportunityIndex].layoutBox() == &m_inlineItems[index].layoutBox());
lineCandidate.inlineContent.appendRubyContainerRange({ { softWrapOpportunityIndex, { } }, { index + 1, { } } });
return true;
}
}
ASSERT_NOT_REACHED();
// Let's just skip this corrupt ruby container and treat it as regular inline content.
++softWrapOpportunityIndex;
return false;
};
if (appendRubyContainerIfApplicable())
return;

auto firstInlineTextItemIndex = std::optional<size_t> { };
auto lastInlineTextItemIndex = std::optional<size_t> { };
#if ENABLE(CSS_BOX_DECORATION_BREAK)
Expand Down Expand Up @@ -942,12 +975,14 @@ bool LineBuilder::tryPlacingFloatBox(const Box& floatBox, MayOverConstrainLine m

LineBuilder::Result LineBuilder::handleInlineContent(const InlineItemRange& layoutRange, const LineCandidate& lineCandidate)
{
auto result = LineBuilder::Result { };
auto& inlineContent = lineCandidate.inlineContent;
auto& continuousInlineContent = inlineContent.continuousContent();

if (continuousInlineContent.runs().isEmpty()) {
auto& continuousInlineContent = inlineContent.continuousContent();
if (continuousInlineContent.runs().isEmpty() && !inlineContent.isRuby()) {
ASSERT(inlineContent.trailingLineBreak() || inlineContent.trailingWordBreakOpportunity());
return { inlineContent.trailingLineBreak() ? InlineContentBreaker::IsEndOfLine::Yes : InlineContentBreaker::IsEndOfLine::No };
result = { inlineContent.trailingLineBreak() ? InlineContentBreaker::IsEndOfLine::Yes : InlineContentBreaker::IsEndOfLine::No };
return result;
}

auto usedConstraints = adjustedLineRectWithCandidateInlineContent(lineCandidate);
Expand All @@ -960,29 +995,29 @@ LineBuilder::Result LineBuilder::handleInlineContent(const InlineItemRange& layo
return availableWidth(inlineContent, m_line, availableTotalWidthForContent);
}();

auto lineBreakingResult = InlineContentBreaker::Result { InlineContentBreaker::Result::Action::Keep, InlineContentBreaker::IsEndOfLine::No, { }, { } };
if (continuousInlineContent.logicalWidth() > availableWidthForCandidateContent) {
auto lineIsConsideredContentful = m_line.hasContentOrListMarker() || isLineConstrainedByFloat() || !usedConstraints.isConstrainedByFloat.isEmpty();
auto lineStatus = InlineContentBreaker::LineStatus {
m_line.contentLogicalRight(),
availableWidthForCandidateContent,
m_line.trimmableTrailingWidth(),
m_line.trailingSoftHyphenWidth(),
m_line.isTrailingRunFullyTrimmable(),
lineIsConsideredContentful,
!m_wrapOpportunityList.isEmpty()
};
lineBreakingResult = inlineContentBreaker().processInlineContent(continuousInlineContent, lineStatus);
auto lineGainsNewContent = true;
if (!inlineContent.isRuby()) {
auto lineBreakingResult = InlineContentBreaker::Result { InlineContentBreaker::Result::Action::Keep, InlineContentBreaker::IsEndOfLine::No, { }, { } };
if (continuousInlineContent.logicalWidth() > availableWidthForCandidateContent) {
auto lineIsConsideredContentful = m_line.hasContentOrListMarker() || isLineConstrainedByFloat() || !usedConstraints.isConstrainedByFloat.isEmpty();
auto lineStatus = InlineContentBreaker::LineStatus { m_line.contentLogicalRight(), availableWidthForCandidateContent, m_line.trimmableTrailingWidth(), m_line.trailingSoftHyphenWidth(), m_line.isTrailingRunFullyTrimmable(), lineIsConsideredContentful, !m_wrapOpportunityList.isEmpty() };
lineBreakingResult = inlineContentBreaker().processInlineContent(continuousInlineContent, lineStatus);
lineGainsNewContent = lineBreakingResult.action == InlineContentBreaker::Result::Action::Keep || lineBreakingResult.action == InlineContentBreaker::Result::Action::Break;
}
result = processLineBreakingResult(lineCandidate, layoutRange, lineBreakingResult);
} else {
result = handleRubyContent(inlineContent.rubyContainerRange(), availableWidthForCandidateContent);
lineGainsNewContent = !!result.committedCount.value;
}
auto lineGainsNewContent = lineBreakingResult.action == InlineContentBreaker::Result::Action::Keep || lineBreakingResult.action == InlineContentBreaker::Result::Action::Break;

if (lineGainsNewContent) {
// Sometimes in order to put this content on the line, we have to avoid additional float boxes (when the new content is taller than any previous content and we have vertically stacked floats on this line)
// which means we need to adjust the line rect to accommodate such new constraints.
m_lineLogicalRect = usedConstraints.logicalRect;
m_lineIsConstrainedByFloat.add(usedConstraints.isConstrainedByFloat);
}
m_candidateContentMaximumHeight = usedConstraints.logicalRect.height();
return processLineBreakingResult(lineCandidate, layoutRange, lineBreakingResult);
return result;
}

LineBuilder::Result LineBuilder::processLineBreakingResult(const LineCandidate& lineCandidate, const InlineItemRange& layoutRange, const InlineContentBreaker::Result& lineBreakingResult)
Expand Down Expand Up @@ -1075,6 +1110,22 @@ LineBuilder::Result LineBuilder::processLineBreakingResult(const LineCandidate&
return { InlineContentBreaker::IsEndOfLine::No };
}

LineBuilder::Result LineBuilder::handleRubyContent(const InlineItemRange& rubyContainerRange, InlineLayoutUnit availableWidthForCandidateContent)
{
// This is where we create a ruby formatting context which will deal with the ruby content by placing base runs on the current line.
UNUSED_PARAM(availableWidthForCandidateContent);

auto& formattingGeometry = formattingContext().formattingGeometry();
auto currentLogicalRight = m_line.contentLogicalRight();
for (auto index = rubyContainerRange.startIndex(); index < rubyContainerRange.endIndex(); ++index) {
auto& inlineItem = m_inlineItems[index];
auto inlineItemWidth = formattingGeometry.inlineItemWidth(inlineItem, currentLogicalRight, isFirstFormattedLine());
m_line.append(inlineItem, inlineItem.layoutBox().style(), inlineItemWidth);
currentLogicalRight += inlineItemWidth;
}
return { InlineContentBreaker::IsEndOfLine::No, { rubyContainerRange.endIndex() - rubyContainerRange.startIndex(), false } };
}

void LineBuilder::commitPartialContent(const InlineContentBreaker::ContinuousContent::RunList& runs, const InlineContentBreaker::Result::PartialTrailingContent& partialTrailingContent)
{
for (size_t index = 0; index < runs.size(); ++index) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class LineBuilder : public AbstractLineBuilder {
enum MayOverConstrainLine : uint8_t { No, Yes, OnlyWhenFirstFloatOnLine };
bool tryPlacingFloatBox(const Box&, MayOverConstrainLine);
Result handleInlineContent(const InlineItemRange& needsLayoutRange, const LineCandidate&);
Result handleRubyContent(const InlineItemRange& rubyContainerRange, InlineLayoutUnit availableWidthForCandidateContent);
Result processLineBreakingResult(const LineCandidate&, const InlineItemRange& layoutRange, const InlineContentBreaker::Result&);
UsedConstraints adjustedLineRectWithCandidateInlineContent(const LineCandidate&) const;
size_t rebuildLineWithInlineContent(const InlineItemRange& needsLayoutRange, const InlineItem& lastInlineItemToAdd);
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/layout/layouttree/LayoutBox.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class Box : public CanMakeCheckedPtr {

bool isDocumentBox() const { return m_nodeType == NodeType::DocumentElement; }
bool isBodyBox() const { return m_nodeType == NodeType::Body; }
bool isRuby() const { return style().display() == DisplayType::Ruby; }
bool isTableWrapperBox() const { return m_nodeType == NodeType::TableWrapperBox; }
bool isTableBox() const { return m_nodeType == NodeType::TableBox; }
bool isTableCaption() const { return style().display() == DisplayType::TableCaption; }
Expand Down

0 comments on commit ab6f97a

Please sign in to comment.