summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/layout/ng/inline
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/core/layout/ng/inline')
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc224
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h68
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc8
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc32
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h1
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position_test.cc19
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc157
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h14
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc318
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h2
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc3
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc124
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h41
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc31
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h42
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc15
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h9
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc7
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc230
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h2
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc17
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc152
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h3
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc10
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc29
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h3
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc845
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h210
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc9
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.cc14
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h9
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc4
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc75
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h26
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc17
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc58
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h18
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc61
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc4
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h2
40 files changed, 1982 insertions, 931 deletions
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc
new file mode 100644
index 00000000000..614d0403810
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc
@@ -0,0 +1,224 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h"
+
+#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
+#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
+#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment_traversal.h"
+#include "third_party/blink/renderer/platform/fonts/character_range.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h"
+
+namespace blink {
+
+NGAbstractInlineTextBox::FragmentToNGAbstractInlineTextBoxHashMap*
+ NGAbstractInlineTextBox::g_abstract_inline_text_box_map_ = nullptr;
+
+scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::GetOrCreate(
+ LineLayoutText line_layout_item,
+ const NGPaintFragment& fragment) {
+ DCHECK(fragment.GetLayoutObject()->IsText()) << fragment.GetLayoutObject();
+ if (!g_abstract_inline_text_box_map_) {
+ g_abstract_inline_text_box_map_ =
+ new FragmentToNGAbstractInlineTextBoxHashMap();
+ }
+ const auto it = g_abstract_inline_text_box_map_->find(&fragment);
+ if (it != g_abstract_inline_text_box_map_->end())
+ return it->value;
+ scoped_refptr<AbstractInlineTextBox> obj =
+ base::AdoptRef(new NGAbstractInlineTextBox(line_layout_item, fragment));
+ g_abstract_inline_text_box_map_->Set(&fragment, obj);
+ return obj;
+}
+
+void NGAbstractInlineTextBox::WillDestroy(NGPaintFragment* fragment) {
+ if (!g_abstract_inline_text_box_map_)
+ return;
+ const auto it = g_abstract_inline_text_box_map_->find(fragment);
+ if (it != g_abstract_inline_text_box_map_->end()) {
+ it->value->Detach();
+ g_abstract_inline_text_box_map_->erase(fragment);
+ }
+}
+
+NGAbstractInlineTextBox::NGAbstractInlineTextBox(
+ LineLayoutText line_layout_item,
+ const NGPaintFragment& fragment)
+ : AbstractInlineTextBox(line_layout_item), fragment_(&fragment) {
+ DCHECK(fragment_->PhysicalFragment().IsText()) << fragment_;
+}
+
+NGAbstractInlineTextBox::~NGAbstractInlineTextBox() {
+ DCHECK(!fragment_);
+}
+
+void NGAbstractInlineTextBox::Detach() {
+ if (Node* const node = GetNode()) {
+ if (AXObjectCache* cache = node->GetDocument().ExistingAXObjectCache())
+ cache->InlineTextBoxesUpdated(GetLineLayoutItem());
+ }
+ AbstractInlineTextBox::Detach();
+ fragment_ = nullptr;
+}
+
+bool NGAbstractInlineTextBox::HasSoftWrapToNextLine() const {
+ return ToNGPhysicalLineBoxFragment(
+ fragment_->ContainerLineBox()->PhysicalFragment())
+ .HasSoftWrapToNextLine();
+}
+
+const NGPhysicalTextFragment& NGAbstractInlineTextBox::PhysicalTextFragment()
+ const {
+ return ToNGPhysicalTextFragment(fragment_->PhysicalFragment());
+}
+
+bool NGAbstractInlineTextBox::NeedsLayout() const {
+ return fragment_->GetLayoutObject()->NeedsLayout();
+}
+
+bool NGAbstractInlineTextBox::NeedsTrailingSpace() const {
+ if (!HasSoftWrapToNextLine())
+ return false;
+ const NGPaintFragment* next_fragment = NextTextFragmentForSameLayoutObject();
+ if (!next_fragment)
+ return false;
+ return ToNGPhysicalTextFragment(next_fragment->PhysicalFragment())
+ .StartOffset() != PhysicalTextFragment().EndOffset();
+}
+
+const NGPaintFragment*
+NGAbstractInlineTextBox::NextTextFragmentForSameLayoutObject() const {
+ const auto fragments =
+ NGPaintFragment::InlineFragmentsFor(fragment_->GetLayoutObject());
+ const auto it =
+ std::find_if(fragments.begin(), fragments.end(),
+ [&](const auto& sibling) { return fragment_ == sibling; });
+ DCHECK(it != fragments.end());
+ const auto next_it = std::next(it);
+ return next_it == fragments.end() ? nullptr : *next_it;
+}
+
+scoped_refptr<AbstractInlineTextBox>
+NGAbstractInlineTextBox::NextInlineTextBox() const {
+ if (!fragment_)
+ return nullptr;
+ DCHECK(!NeedsLayout());
+ const NGPaintFragment* next_fragment = NextTextFragmentForSameLayoutObject();
+ if (!next_fragment)
+ return nullptr;
+ return GetOrCreate(GetLineLayoutItem(), *next_fragment);
+}
+
+LayoutRect NGAbstractInlineTextBox::LocalBounds() const {
+ if (!fragment_ || !GetLineLayoutItem())
+ return LayoutRect();
+ return LayoutRect(fragment_->InlineOffsetToContainerBox().ToLayoutPoint(),
+ fragment_->Size().ToLayoutSize());
+}
+
+unsigned NGAbstractInlineTextBox::Len() const {
+ if (!fragment_)
+ return 0;
+ if (NeedsTrailingSpace())
+ return PhysicalTextFragment().Length() + 1;
+ return PhysicalTextFragment().Length();
+}
+
+AbstractInlineTextBox::Direction NGAbstractInlineTextBox::GetDirection() const {
+ if (!fragment_ || !GetLineLayoutItem())
+ return kLeftToRight;
+ const TextDirection text_direction =
+ PhysicalTextFragment().ResolvedDirection();
+ if (GetLineLayoutItem().Style()->IsHorizontalWritingMode())
+ return IsLtr(text_direction) ? kLeftToRight : kRightToLeft;
+ return IsLtr(text_direction) ? kTopToBottom : kBottomToTop;
+}
+
+void NGAbstractInlineTextBox::CharacterWidths(Vector<float>& widths) const {
+ if (!fragment_)
+ return;
+ if (!PhysicalTextFragment().TextShapeResult()) {
+ // When |fragment_| for BR, we don't have shape result.
+ // "aom-computed-boolean-properties.html" reaches here.
+ widths.resize(Len());
+ return;
+ }
+ const ShapeResult& shape_result = *PhysicalTextFragment().TextShapeResult();
+ ShapeResultBuffer buffer;
+ buffer.AppendResult(&shape_result);
+ const Vector<CharacterRange> ranges = buffer.IndividualCharacterRanges(
+ shape_result.Direction(), shape_result.Width());
+ widths.ReserveCapacity(ranges.size());
+ widths.resize(0);
+ for (const auto& range : ranges)
+ widths.push_back(range.Width());
+ // The shaper can fail to return glyph metrics for all characters (see
+ // crbug.com/613915 and crbug.com/615661) so add empty ranges to ensure all
+ // characters have an associated range.
+ widths.resize(Len());
+}
+
+String NGAbstractInlineTextBox::GetText() const {
+ if (!fragment_ || !GetLineLayoutItem())
+ return String();
+ // For compatibility with |InlineTextBox|, we should have a space character
+ // for soft line break.
+ // Following tests require this:
+ // - accessibility/inline-text-change-style.html
+ // - accessibility/inline-text-changes.html
+ // - accessibility/inline-text-word-boundaries.html
+ if (NeedsTrailingSpace())
+ return PhysicalTextFragment().Text().ToString() + " ";
+ return PhysicalTextFragment().Text().ToString();
+}
+
+bool NGAbstractInlineTextBox::IsFirst() const {
+ if (!fragment_)
+ return true;
+ DCHECK(!NeedsLayout());
+ const auto fragments =
+ NGPaintFragment::InlineFragmentsFor(fragment_->GetLayoutObject());
+ return fragment_ == &fragments.front();
+}
+
+bool NGAbstractInlineTextBox::IsLast() const {
+ if (!fragment_)
+ return true;
+ DCHECK(!NeedsLayout());
+ const auto fragments =
+ NGPaintFragment::InlineFragmentsFor(fragment_->GetLayoutObject());
+ return fragment_ == &fragments.back();
+}
+
+scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::NextOnLine()
+ const {
+ if (!fragment_)
+ return nullptr;
+ DCHECK(!NeedsLayout());
+ DCHECK(fragment_->ContainerLineBox());
+ NGPaintFragmentTraversal cursor(*fragment_->ContainerLineBox(), *fragment_);
+ for (cursor.MoveToNext(); !cursor.IsAtEnd(); cursor.MoveToNext()) {
+ if (cursor->GetLayoutObject()->IsText())
+ return GetOrCreate(GetLineLayoutItem(), *cursor);
+ }
+ return nullptr;
+}
+
+scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::PreviousOnLine()
+ const {
+ if (!fragment_)
+ return nullptr;
+ DCHECK(!NeedsLayout());
+ DCHECK(fragment_->ContainerLineBox());
+ NGPaintFragmentTraversal cursor(*fragment_->ContainerLineBox(), *fragment_);
+ for (cursor.MoveToPrevious(); !cursor.IsAtEnd(); cursor.MoveToPrevious()) {
+ if (cursor->GetLayoutObject()->IsText())
+ return GetOrCreate(GetLineLayoutItem(), *cursor);
+ }
+ return nullptr;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h
new file mode 100644
index 00000000000..4932dec9d7d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h
@@ -0,0 +1,68 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_ABSTRACT_INLINE_TEXT_BOX_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_ABSTRACT_INLINE_TEXT_BOX_H_
+
+#include "third_party/blink/renderer/core/layout/line/abstract_inline_text_box.h"
+
+namespace blink {
+
+class NGPaintFragment;
+class NGPhysicalTextFragment;
+
+// The implementation of |AbstractInlineTextBox| for LayoutNG.
+// See also |LegacyAbstractInlineTextBox| for legacy layout.
+class CORE_EXPORT NGAbstractInlineTextBox final : public AbstractInlineTextBox {
+ private:
+ // Returns existing or newly created |NGAbstractInlineTextBox|.
+ // * |line_layout_item| is |LayoutText| associated to |fragment|. For first
+ // letter part, it is remaining part of |LayoutTextFragment|.
+ // * |fragment| should be attached to |NGPhysicalTextFragment|.
+ static scoped_refptr<AbstractInlineTextBox> GetOrCreate(
+ LineLayoutText line_layout_item,
+ const NGPaintFragment& fragment);
+ static void WillDestroy(NGPaintFragment*);
+
+ friend class LayoutText;
+ friend class NGPaintFragment;
+
+ public:
+ ~NGAbstractInlineTextBox() final;
+
+ private:
+ NGAbstractInlineTextBox(LineLayoutText line_layout_item,
+ const NGPaintFragment& fragment);
+
+ bool HasSoftWrapToNextLine() const;
+ const NGPhysicalTextFragment& PhysicalTextFragment() const;
+ bool NeedsLayout() const;
+ bool NeedsTrailingSpace() const;
+ // Returns next fragment associated to |LayoutText|.
+ const NGPaintFragment* NextTextFragmentForSameLayoutObject() const;
+
+ // Implementations of AbstractInlineTextBox member functions.
+ void Detach() final;
+ scoped_refptr<AbstractInlineTextBox> NextInlineTextBox() const final;
+ LayoutRect LocalBounds() const final;
+ unsigned Len() const final;
+ Direction GetDirection() const final;
+ void CharacterWidths(Vector<float>&) const final;
+ String GetText() const final;
+ bool IsFirst() const final;
+ bool IsLast() const final;
+ scoped_refptr<AbstractInlineTextBox> NextOnLine() const final;
+ scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const final;
+
+ const NGPaintFragment* fragment_;
+
+ using FragmentToNGAbstractInlineTextBoxHashMap =
+ HashMap<const NGPaintFragment*, scoped_refptr<AbstractInlineTextBox>>;
+ static FragmentToNGAbstractInlineTextBoxHashMap*
+ g_abstract_inline_text_box_map_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_ABSTRACT_INLINE_TEXT_BOX_H_
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc
index e43c7bcd284..a65589efaea 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc
@@ -23,7 +23,7 @@ bool NGBaseline::ShouldPropagateBaselines(const NGLayoutInputNode node) {
if (node.IsInline())
return true;
- return ShouldPropagateBaselines(ToLayoutBox(node.GetLayoutObject()));
+ return ShouldPropagateBaselines(node.GetLayoutBox());
}
bool NGBaseline::ShouldPropagateBaselines(LayoutBox* layout_box) {
@@ -39,12 +39,6 @@ bool NGBaseline::ShouldPropagateBaselines(LayoutBox* layout_box) {
if (!NGBlockNode(layout_box).CanUseNewLayout())
return true;
- // CSS defines certain cases to synthesize baselines from box. See comments in
- // UseLogicalBottomMarginEdgeForInlineBlockBaseline().
- const LayoutBlock* layout_block = ToLayoutBlock(layout_box);
- if (layout_block->UseLogicalBottomMarginEdgeForInlineBlockBaseline())
- return false;
-
return true;
}
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc
index cadd9c38992..a139ca08482 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc
@@ -160,7 +160,8 @@ CaretPositionResolution TryResolveCaretPositionByBoxFragmentSide(
const NGPaintFragment& fragment,
unsigned offset,
TextAffinity affinity) {
- if (!fragment.GetNode()) {
+ // There is no caret position at a pseudo or generated box side.
+ if (!fragment.GetNode() || fragment.GetNode()->IsPseudoElement()) {
// TODO(xiaochengh): This leads to false negatives for, e.g., RUBY, where an
// anonymous wrapping inline block is created.
return CaretPositionResolution();
@@ -289,25 +290,40 @@ NGCaretPosition ComputeNGCaretPosition(const PositionWithAffinity& position) {
}
Position NGCaretPosition::ToPositionInDOMTree() const {
+ return ToPositionInDOMTreeWithAffinity().GetPosition();
+}
+
+PositionWithAffinity NGCaretPosition::ToPositionInDOMTreeWithAffinity() const {
if (!fragment)
- return Position();
+ return PositionWithAffinity();
switch (position_type) {
case NGCaretPositionType::kBeforeBox:
if (!fragment->GetNode())
- return Position();
- return Position::BeforeNode(*fragment->GetNode());
+ return PositionWithAffinity();
+ return PositionWithAffinity(Position::BeforeNode(*fragment->GetNode()),
+ TextAffinity::kDownstream);
case NGCaretPositionType::kAfterBox:
if (!fragment->GetNode())
- return Position();
- return Position::AfterNode(*fragment->GetNode());
+ return PositionWithAffinity();
+ return PositionWithAffinity(Position::AfterNode(*fragment->GetNode()),
+ TextAffinity::kUpstreamIfPossible);
case NGCaretPositionType::kAtTextOffset:
DCHECK(text_offset.has_value());
const NGOffsetMapping* mapping =
NGOffsetMapping::GetFor(fragment->GetLayoutObject());
- return mapping->GetFirstPosition(*text_offset);
+ const Position position = mapping->GetFirstPosition(*text_offset);
+ if (position.IsNull())
+ return PositionWithAffinity();
+ const NGPhysicalTextFragment& text_fragment =
+ ToNGPhysicalTextFragment(fragment->PhysicalFragment());
+ const TextAffinity affinity =
+ text_offset.value() == text_fragment.EndOffset()
+ ? TextAffinity::kUpstreamIfPossible
+ : TextAffinity::kDownstream;
+ return PositionWithAffinity(position, affinity);
}
NOTREACHED();
- return Position();
+ return PositionWithAffinity();
}
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h
index e6a963099c6..cb3beefdfe1 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h
@@ -32,6 +32,7 @@ struct NGCaretPosition {
bool IsNull() const { return !fragment; }
Position ToPositionInDOMTree() const;
+ PositionWithAffinity ToPositionInDOMTreeWithAffinity() const;
const NGPaintFragment* fragment = nullptr; // owned by root LayoutNGMixin
NGCaretPositionType position_type;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position_test.cc
index da232c2bf83..6446ddb34de 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position_test.cc
@@ -276,4 +276,23 @@ TEST_F(NGCaretPositionTest, CaretPositionAtSoftLineWrapBetweenDeepTextNodes) {
fragment_d, kAtTextOffset, base::Optional<unsigned>(wrap_offset));
}
+TEST_F(NGCaretPositionTest, InlineBlockBeforeContent) {
+ SetInlineFormattingContext(
+ "t",
+ "<style>span::before{display:inline-block; content:'foo'}</style>"
+ "<span id=span>bar</span>",
+ 100); // Line width doesn't matter here.
+ const Node* text = GetElementById("span")->firstChild();
+ const NGPhysicalFragment* text_fragment = FragmentOf(text);
+
+ // Test caret position of "|bar", which shouldn't be affected by ::before
+ const Position position(text, 0);
+ const NGOffsetMapping& mapping = *NGOffsetMapping::GetFor(position);
+ const unsigned text_offset = mapping.GetTextContentOffset(position).value();
+
+ TEST_CARET(ComputeNGCaretPosition(text_offset, TextAffinity::kDownstream),
+ text_fragment, kAtTextOffset,
+ base::Optional<unsigned>(text_offset));
+}
+
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
index fca16e8c347..97202bc6428 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
@@ -15,12 +15,41 @@
namespace blink {
+namespace {
+
+NGLineHeightMetrics ComputeEmphasisMarkOutsets(const ComputedStyle& style) {
+ if (style.GetTextEmphasisMark() == TextEmphasisMark::kNone)
+ return NGLineHeightMetrics();
+
+ const Font& font = style.GetFont();
+ LayoutUnit emphasis_mark_height =
+ LayoutUnit(font.EmphasisMarkHeight(style.TextEmphasisMarkString()));
+ DCHECK_GT(emphasis_mark_height, LayoutUnit());
+ return style.GetTextEmphasisLineLogicalSide() == LineLogicalSide::kOver
+ ? NGLineHeightMetrics(emphasis_mark_height, LayoutUnit())
+ : NGLineHeightMetrics(LayoutUnit(), emphasis_mark_height);
+}
+
+} // namespace
+
void NGInlineBoxState::ComputeTextMetrics(const ComputedStyle& style,
FontBaseline baseline_type) {
text_metrics = NGLineHeightMetrics(style, baseline_type);
text_top = -text_metrics.ascent;
text_height = text_metrics.LineHeight();
- text_metrics.AddLeading(style.ComputedLineHeightAsFixed());
+
+ NGLineHeightMetrics emphasis_marks_outsets =
+ ComputeEmphasisMarkOutsets(style);
+ if (emphasis_marks_outsets.IsEmpty()) {
+ text_metrics.AddLeading(style.ComputedLineHeightAsFixed());
+ } else {
+ NGLineHeightMetrics emphasis_marks_metrics = text_metrics;
+ emphasis_marks_metrics += emphasis_marks_outsets;
+ text_metrics.AddLeading(style.ComputedLineHeightAsFixed());
+ text_metrics.Unite(emphasis_marks_metrics);
+ // TODO: Is this correct to include into text_metrics? How do we use
+ // text_metrics after this point?
+ }
metrics.Unite(text_metrics);
@@ -37,7 +66,7 @@ void NGInlineBoxState::AccumulateUsedFonts(const ShapeResult* shape_result,
FontBaseline baseline_type) {
HashSet<const SimpleFontData*> fallback_fonts;
shape_result->FallbackFonts(&fallback_fonts);
- for (auto* const fallback_font : fallback_fonts) {
+ for (const SimpleFontData* const fallback_font : fallback_fonts) {
NGLineHeightMetrics fallback_metrics(fallback_font->GetFontMetrics(),
baseline_type);
fallback_metrics.AddLeading(
@@ -61,7 +90,7 @@ LayoutObject*
NGInlineLayoutStateStack::ContainingLayoutObjectForAbsolutePositionObjects()
const {
for (unsigned i = stack_.size(); i-- > 1;) {
- const auto& box = stack_[i];
+ const NGInlineBoxState& box = stack_[i];
DCHECK(box.style);
if (box.style->CanContainAbsolutePositionObjects()) {
DCHECK(box.item->GetLayoutObject());
@@ -82,7 +111,7 @@ NGInlineBoxState* NGInlineLayoutStateStack::OnBeginPlaceItems(
box->fragment_start = 0;
} else {
// For the following lines, clear states that are not shared across lines.
- for (auto& box : stack_) {
+ for (NGInlineBoxState& box : stack_) {
box.fragment_start = 0;
if (!line_height_quirk)
box.metrics = box.text_metrics;
@@ -136,8 +165,8 @@ NGInlineBoxState* NGInlineLayoutStateStack::OnOpenTag(
DCHECK_EQ(item_result.margins.inline_start, LayoutUnit());
DCHECK_EQ(item_result.inline_size, LayoutUnit());
}
- box->border_padding_block_start = item_result.borders_paddings_block_start;
- box->border_padding_block_end = item_result.borders_paddings_block_end;
+ box->border_padding_line_over = item_result.borders_paddings_line_over;
+ box->border_padding_line_under = item_result.borders_paddings_line_under;
return box;
}
@@ -182,10 +211,21 @@ void NGInlineLayoutStateStack::EndBoxState(
PositionPending position_pending =
ApplyBaselineShift(box, line_box, baseline_type);
+ // We are done here if there is no parent box.
+ if (box == stack_.begin())
+ return;
+ NGInlineBoxState& parent_box = *std::prev(box);
+
+ // Propagate necessary data back to the parent box.
+
// Unite the metrics to the parent box.
- if (position_pending == kPositionNotPending && box != stack_.begin()) {
- box[-1].metrics.Unite(box->metrics);
- }
+ if (position_pending == kPositionNotPending)
+ parent_box.metrics.Unite(box->metrics);
+
+ // Create box fragments for parent if the current box has properties (e.g.,
+ // margin) that make it tricky to compute the parent's rects.
+ if (box->ParentNeedsBoxFragment(parent_box))
+ parent_box.SetNeedsBoxFragment();
}
void NGInlineBoxState::SetNeedsBoxFragment() {
@@ -208,6 +248,23 @@ void NGInlineBoxState::SetLineRightForBoxFragment(
}
}
+bool NGInlineBoxState::ParentNeedsBoxFragment(
+ const NGInlineBoxState& parent) const {
+ if (!parent.item)
+ return false;
+ // Below are the known cases where parent rect may not equal the union of
+ // its child rects.
+ if (margin_inline_start || margin_inline_end)
+ return true;
+ // Inline box height is determined by font metrics, which can be different
+ // from the height of its child atomic inline.
+ if (item && item->Type() == NGInlineItem::kAtomicInline)
+ return true;
+ // Returns true when parent and child boxes have different font metrics, since
+ // they may have different heights and/or locations in block direction.
+ return text_metrics != parent.text_metrics;
+}
+
// Crete a placeholder for a box fragment.
// We keep a flat list of fragments because it is more suitable for operations
// such as ApplyBaselineShift. Later, CreateBoxFragments() creates box fragments
@@ -228,10 +285,10 @@ void NGInlineLayoutStateStack::AddBoxFragmentPlaceholder(
// Extend the block direction of the box by borders and paddings. Inline
// direction is already included into positions in NGLineBreaker.
NGLogicalOffset offset(LayoutUnit(),
- -metrics.ascent - box->border_padding_block_start);
+ -metrics.ascent - box->border_padding_line_over);
NGLogicalSize size(LayoutUnit(), metrics.LineHeight() +
- box->border_padding_block_start +
- box->border_padding_block_end);
+ box->border_padding_line_over +
+ box->border_padding_line_under);
unsigned fragment_end = line_box->size();
DCHECK(box->item);
@@ -289,7 +346,7 @@ void NGInlineLayoutStateStack::PrepareForReorder(
NGLineBoxFragmentBuilder::ChildList* line_box) {
// Set indexes of BoxData to the children of the line box.
unsigned box_data_index = 0;
- for (const auto& box_data : box_data_list_) {
+ for (const BoxData& box_data : box_data_list_) {
box_data_index++;
for (unsigned i = box_data.fragment_start; i < box_data.fragment_end; i++) {
NGLineBoxFragmentBuilder::Child& child = (*line_box)[i];
@@ -300,7 +357,7 @@ void NGInlineLayoutStateStack::PrepareForReorder(
// When boxes are nested, placeholders have indexes to which box it should be
// added. Copy them to BoxData.
- for (auto& box_data : box_data_list_) {
+ for (BoxData& box_data : box_data_list_) {
const NGLineBoxFragmentBuilder::Child& placeholder =
(*line_box)[box_data.fragment_end];
DCHECK(!placeholder.HasFragment());
@@ -312,11 +369,11 @@ void NGInlineLayoutStateStack::PrepareForReorder(
void NGInlineLayoutStateStack::UpdateAfterReorder(
NGLineBoxFragmentBuilder::ChildList* line_box) {
// Compute start/end of boxes from the children of the line box.
- for (auto& box_data : box_data_list_)
+ for (BoxData& box_data : box_data_list_)
box_data.fragment_start = box_data.fragment_end = 0;
for (unsigned i = 0; i < line_box->size(); i++) {
- const auto& child = (*line_box)[i];
- if (!child.HasFragment())
+ const NGLineBoxFragmentBuilder::Child& child = (*line_box)[i];
+ if (child.IsPlaceholder())
continue;
if (unsigned box_data_index = child.box_data_index) {
BoxData& box_data = box_data_list_[box_data_index - 1];
@@ -327,7 +384,7 @@ void NGInlineLayoutStateStack::UpdateAfterReorder(
}
// Extend start/end of boxes when they are nested.
- for (auto& box_data : box_data_list_) {
+ for (BoxData& box_data : box_data_list_) {
if (box_data.box_data_index) {
BoxData& parent_box_data = box_data_list_[box_data.box_data_index - 1];
if (!parent_box_data.fragment_end) {
@@ -344,7 +401,7 @@ void NGInlineLayoutStateStack::UpdateAfterReorder(
#if DCHECK_IS_ON()
// Check all BoxData have ranges.
- for (const auto& box_data : box_data_list_) {
+ for (const BoxData& box_data : box_data_list_) {
DCHECK_NE(box_data.fragment_end, 0u);
DCHECK_GT(box_data.fragment_end, box_data.fragment_start);
}
@@ -356,7 +413,7 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions(
// At this point, children are in the visual order, and they have their
// origins at (0, 0). Accumulate inline offset from left to right.
LayoutUnit position;
- for (auto& child : *line_box) {
+ for (NGLineBoxFragmentBuilder::Child& child : *line_box) {
child.offset.inline_offset += position;
// Box margins/boders/paddings will be processed later.
// TODO(kojii): we could optimize this if the reordering did not occur.
@@ -368,27 +425,12 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions(
if (box_data_list_.IsEmpty())
return position;
- // Compute inline positions of inline boxes.
- for (auto& box_data : box_data_list_) {
+ // Adjust child offsets for margin/border/padding of inline boxes.
+ for (BoxData& box_data : box_data_list_) {
unsigned start = box_data.fragment_start;
unsigned end = box_data.fragment_end;
DCHECK_GT(end, start);
- NGLineBoxFragmentBuilder::Child& start_child = (*line_box)[start];
-
- // Clamping left offset is not defined, match to the existing behavior.
- LayoutUnit line_left_offset =
- start_child.offset.inline_offset.ClampNegativeToZero();
- LayoutUnit line_right_offset = end < line_box->size()
- ? (*line_box)[end].offset.inline_offset
- : position;
- box_data.offset.inline_offset =
- line_left_offset + box_data.margin_line_left;
- box_data.size.inline_size =
- line_right_offset - line_left_offset +
- box_data.margin_border_padding_line_left - box_data.margin_line_left +
- box_data.margin_border_padding_line_right - box_data.margin_line_right;
- // Adjust child offsets for margin/border/padding.
if (box_data.margin_border_padding_line_left) {
line_box->MoveInInlineDirection(box_data.margin_border_padding_line_left,
start, line_box->size());
@@ -402,6 +444,39 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions(
}
}
+ // Compute positions and sizes of inline boxes.
+ //
+ // Accumulate margin/border/padding of boxes for each child, to place nested
+ // parent boxes relative to the leaf (text or atomic inline) child.
+ struct LinePadding {
+ LayoutUnit line_left;
+ LayoutUnit line_right;
+ };
+ Vector<LinePadding, 32> accumulated_padding(line_box->size());
+ for (BoxData& box_data : box_data_list_) {
+ // Compute line-left and line-right edge of this box by accomodating
+ // border/padding of this box and margin/border/padding of descendants
+ // boxes, while accumulating its margin/border/padding.
+ unsigned start = box_data.fragment_start;
+ NGLineBoxFragmentBuilder::Child& start_child = (*line_box)[start];
+ LayoutUnit line_left_offset = start_child.offset.inline_offset;
+ LinePadding& start_padding = accumulated_padding[start];
+ start_padding.line_left += box_data.margin_border_padding_line_left;
+ line_left_offset -= start_padding.line_left - box_data.margin_line_left;
+
+ DCHECK_GT(box_data.fragment_end, start);
+ unsigned last = box_data.fragment_end - 1;
+ NGLineBoxFragmentBuilder::Child& last_child = (*line_box)[last];
+ LayoutUnit line_right_offset =
+ last_child.offset.inline_offset + last_child.inline_size;
+ LinePadding& last_padding = accumulated_padding[last];
+ last_padding.line_right += box_data.margin_border_padding_line_right;
+ line_right_offset += last_padding.line_right - box_data.margin_line_right;
+
+ box_data.offset.inline_offset = line_left_offset;
+ box_data.size.inline_size = line_right_offset - line_left_offset;
+ }
+
return position;
}
@@ -409,7 +484,7 @@ void NGInlineLayoutStateStack::CreateBoxFragments(
NGLineBoxFragmentBuilder::ChildList* line_box) {
DCHECK(!box_data_list_.IsEmpty());
- for (auto& box_data : box_data_list_) {
+ for (BoxData& box_data : box_data_list_) {
unsigned start = box_data.fragment_start;
unsigned end = box_data.fragment_end;
DCHECK_GT(end, start);
@@ -463,8 +538,8 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment(
// NGInlineLayoutAlgorithm can handle them later.
DCHECK(!child.HasInFlowFragment());
}
-
- return box.ToBoxFragment();
+ box.MoveOutOfFlowDescendantCandidatesToDescendants();
+ return box.ToInlineBoxFragment();
}
NGInlineLayoutStateStack::PositionPending
@@ -477,7 +552,7 @@ NGInlineLayoutStateStack::ApplyBaselineShift(
// |pending_descendants|.
LayoutUnit baseline_shift;
if (!box->pending_descendants.IsEmpty()) {
- for (auto& child : box->pending_descendants) {
+ for (NGPendingPositions& child : box->pending_descendants) {
if (child.metrics.IsEmpty()) {
// This can happen with boxes with no content in quirks mode
child.metrics = NGLineHeightMetrics(LayoutUnit(), LayoutUnit());
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
index 626ad129d87..bfd5b61b6e5 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
@@ -57,14 +57,14 @@ struct NGInlineBoxState {
// is set.
bool has_start_edge = false;
bool has_end_edge = false;
- NGBoxStrut padding;
+ NGLineBoxStrut padding;
// |CreateBoxFragment()| needs margin, border+padding, and the sum of them.
LayoutUnit margin_inline_start;
LayoutUnit margin_inline_end;
LayoutUnit margin_border_padding_inline_start;
LayoutUnit margin_border_padding_inline_end;
- LayoutUnit border_padding_block_start;
- LayoutUnit border_padding_block_end;
+ LayoutUnit border_padding_line_over;
+ LayoutUnit border_padding_line_under;
Vector<NGPendingPositions> pending_descendants;
bool include_used_fonts = false;
@@ -91,6 +91,12 @@ struct NGInlineBoxState {
void SetLineRightForBoxFragment(const NGInlineItem&,
const NGInlineItemResult&);
+ // In certain circumstances, the parent's rects is not a simple union of its
+ // children fragments' rects, e.g., when children have margin. In such cases,
+ // we should create box fragments for the parent to avoid hacky fixup when
+ // computing its rects.
+ bool ParentNeedsBoxFragment(const NGInlineBoxState& parent) const;
+
// Returns if the text style can be added without open-tag.
// Text with different font or vertical-align needs to be wrapped with an
// inline box.
@@ -179,7 +185,7 @@ class CORE_EXPORT NGInlineLayoutStateStack {
bool has_line_left_edge = false;
bool has_line_right_edge = false;
- NGBoxStrut padding;
+ NGLineBoxStrut padding;
// |CreateBoxFragment()| needs margin, border+padding, and the sum of them.
LayoutUnit margin_line_left;
LayoutUnit margin_line_right;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc
index 353acbabc68..6ce8dc88622 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc
@@ -14,70 +14,153 @@ namespace {
using Result = NGPhysicalFragmentWithOffset;
-// Traverse the subtree of |container|, and collect the fragments satisfying
-// |filter| into the |results| vector. Guarantees to call |filter.AddOnEnter()|
-// for all fragments in preorder, and call |filter.RemoveOnExit()| on all
-// fragments in postorder. A fragment is collected if |AddOnEnter()| returns
-// true and |RemoveOnExit()| returns false on it.
-template <typename Filter, size_t inline_capacity>
-void CollectInlineFragments(const NGPhysicalContainerFragment& container,
- NGPhysicalOffset offset_to_container_box,
- Filter& filter,
- Vector<Result, inline_capacity>* results) {
- DCHECK(container.IsInline() || container.IsLineBox() ||
- (container.IsBlockFlow() &&
- ToNGPhysicalBoxFragment(container).ChildrenInline()));
- for (const auto& child : container.Children()) {
- NGPhysicalOffset child_offset = child->Offset() + offset_to_container_box;
-
- if (filter.AddOnEnter(child.get())) {
- results->push_back(
- NGPhysicalFragmentWithOffset{child.get(), child_offset});
- }
+class NGPhysicalFragmentCollectorBase {
+ STACK_ALLOCATED();
+
+ public:
+ virtual Vector<Result> CollectFrom(const NGPhysicalFragment&) = 0;
+
+ protected:
+ explicit NGPhysicalFragmentCollectorBase() = default;
+
+ virtual void Visit() = 0;
+
+ const NGPhysicalFragment& GetFragment() const { return *current_fragment_; }
+ void SetShouldStopTraversing() { should_stop_traversing_ = true; }
+ bool HasStoppedTraversing() const { return should_stop_traversing_; }
+
+ void Emit() {
+ results_.push_back(Result{current_fragment_, current_offset_to_root_});
+ }
+
+ // Visits and collets fragments in the subtree rooted at |fragment|.
+ // |fragment| itself is not visited.
+ Vector<Result> CollectExclusivelyFrom(const NGPhysicalFragment& fragment) {
+ current_fragment_ = &fragment;
+ root_fragment_ = &fragment;
+ VisitChildren();
+ return std::move(results_);
+ }
+
+ // Visits and collets fragments in the subtree rooted at |fragment|.
+ // |fragment| itself is visited.
+ Vector<Result> CollectInclusivelyFrom(const NGPhysicalFragment& fragment) {
+ current_fragment_ = &fragment;
+ root_fragment_ = &fragment;
+ Visit();
+ return std::move(results_);
+ }
+
+ void VisitChildren() {
+ if (should_stop_traversing_)
+ return;
+
+ const NGPhysicalFragment& fragment = *current_fragment_;
+ if (!fragment.IsContainer())
+ return;
// Traverse descendants unless the fragment is laid out separately from the
// inline layout algorithm.
- if (child->IsContainer() && !child->IsBlockLayoutRoot()) {
- CollectInlineFragments(ToNGPhysicalContainerFragment(*child),
- child_offset, filter, results);
- }
+ if (&fragment != root_fragment_ && fragment.IsBlockLayoutRoot())
+ return;
+
+ DCHECK(fragment.IsContainer());
+ DCHECK(fragment.IsInline() || fragment.IsLineBox() ||
+ (fragment.IsBlockFlow() &&
+ ToNGPhysicalBoxFragment(fragment).ChildrenInline()));
- if (filter.RemoveOnExit(child.get())) {
- DCHECK(results->size());
- DCHECK_EQ(results->back().fragment, child.get());
- results->pop_back();
+ for (const auto& child :
+ ToNGPhysicalContainerFragment(fragment).Children()) {
+ base::AutoReset<NGPhysicalOffset> offset_resetter(
+ &current_offset_to_root_, current_offset_to_root_ + child->Offset());
+ base::AutoReset<const NGPhysicalFragment*> fragment_resetter(
+ &current_fragment_, child.get());
+ Visit();
+
+ if (should_stop_traversing_)
+ return;
}
}
-}
-// The filter for CollectInlineFragments() collecting all fragments traversed.
-class AddAllFilter {
+ private:
+ const NGPhysicalFragment* root_fragment_ = nullptr;
+ const NGPhysicalFragment* current_fragment_ = nullptr;
+ NGPhysicalOffset current_offset_to_root_;
+ Vector<Result> results_;
+ bool should_stop_traversing_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(NGPhysicalFragmentCollectorBase);
+};
+
+// The visitor emitting all visited fragments.
+class DescendantCollector final : public NGPhysicalFragmentCollectorBase {
+ STACK_ALLOCATED();
+
public:
- bool AddOnEnter(const NGPhysicalFragment*) const { return true; }
- bool RemoveOnExit(const NGPhysicalFragment*) const { return false; }
+ DescendantCollector() = default;
+
+ Vector<Result> CollectFrom(const NGPhysicalFragment& fragment) final {
+ return CollectExclusivelyFrom(fragment);
+ }
+
+ private:
+ void Visit() final {
+ Emit();
+ VisitChildren();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(DescendantCollector);
};
-// The filter for CollectInlineFragments() collecting fragments generated from
-// the given LayoutInline with supporting culled inline.
+// The visitor emitting all visited fragments.
+class InclusiveDescendantCollector final
+ : public NGPhysicalFragmentCollectorBase {
+ STACK_ALLOCATED();
+
+ public:
+ InclusiveDescendantCollector() = default;
+
+ Vector<Result> CollectFrom(const NGPhysicalFragment& fragment) final {
+ return CollectInclusivelyFrom(fragment);
+ }
+
+ private:
+ void Visit() final {
+ Emit();
+ VisitChildren();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(InclusiveDescendantCollector);
+};
+
+// The visitor emitting fragments generated from the given LayoutInline,
+// supporting culled inline.
// Note: Since we apply culled inline per line, we have a fragment for
// LayoutInline in second line but not in first line in
// "t0803-c5502-imrgn-r-01-b-ag.html".
-class LayoutInlineFilter {
+class LayoutInlineCollector final : public NGPhysicalFragmentCollectorBase {
+ STACK_ALLOCATED();
+
public:
- explicit LayoutInlineFilter(const LayoutInline& container) {
- CollectInclusiveDescendnats(container);
+ explicit LayoutInlineCollector(const LayoutInline& container) {
+ CollectInclusiveDescendants(container);
}
- bool AddOnEnter(const NGPhysicalFragment* fragment) {
- if (fragment->IsLineBox())
- return false;
- return inclusive_descendants_.Contains(fragment->GetLayoutObject());
+ Vector<Result> CollectFrom(const NGPhysicalFragment& fragment) final {
+ return CollectExclusivelyFrom(fragment);
}
- bool RemoveOnExit(const NGPhysicalFragment*) const { return false; }
-
private:
- void CollectInclusiveDescendnats(const LayoutInline& container) {
+ void Visit() final {
+ if (!GetFragment().IsLineBox() &&
+ inclusive_descendants_.Contains(GetFragment().GetLayoutObject())) {
+ Emit();
+ return;
+ }
+ VisitChildren();
+ }
+
+ void CollectInclusiveDescendants(const LayoutInline& container) {
inclusive_descendants_.insert(&container);
for (const LayoutObject* node = container.FirstChild(); node;
node = node->NextSibling()) {
@@ -89,127 +172,136 @@ class LayoutInlineFilter {
}
if (!node->IsLayoutInline())
continue;
- CollectInclusiveDescendnats(ToLayoutInline(*node));
+ CollectInclusiveDescendants(ToLayoutInline(*node));
}
}
HashSet<const LayoutObject*> inclusive_descendants_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayoutInlineCollector);
};
-// The filter for CollectInlineFragments() collecting fragments generated from
-// the given LayoutObject.
-class LayoutObjectFilter {
+// The visitor emitting all fragments generated from the given LayoutObject.
+class LayoutObjectCollector final : public NGPhysicalFragmentCollectorBase {
+ STACK_ALLOCATED();
+
public:
- explicit LayoutObjectFilter(const LayoutObject* layout_object)
- : layout_object_(layout_object) {
- DCHECK(layout_object);
- }
+ explicit LayoutObjectCollector(const LayoutObject* layout_object)
+ : target_(layout_object) {}
- bool AddOnEnter(const NGPhysicalFragment* fragment) const {
- return fragment->GetLayoutObject() == layout_object_;
+ Vector<Result> CollectFrom(const NGPhysicalFragment& fragment) final {
+ return CollectExclusivelyFrom(fragment);
}
- bool RemoveOnExit(const NGPhysicalFragment*) const { return false; }
private:
- const LayoutObject* layout_object_;
+ void Visit() final {
+ if (GetFragment().GetLayoutObject() == target_)
+ Emit();
+ VisitChildren();
+ }
+
+ const LayoutObject* target_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayoutObjectCollector);
};
-// The filter for CollectInlineFragments() collecting inclusive ancestors of the
-// given fragment with the algorithm that, |fragment| is an ancestor of |target|
-// if and only if both of the following are true:
-// - |fragment| precedes |target| in preorder traversal
-// - |fragment| succeeds |target| in postorder traversal
-class InclusiveAncestorFilter {
+// The visitor emitting ancestors of the given fragment in bottom-up order.
+class AncestorCollector : public NGPhysicalFragmentCollectorBase {
+ STACK_ALLOCATED();
+
public:
- explicit InclusiveAncestorFilter(const NGPhysicalFragment& target)
- : target_(&target) {}
+ explicit AncestorCollector(const NGPhysicalFragment& target)
+ : target_(target) {}
- bool AddOnEnter(const NGPhysicalFragment* fragment) {
- if (fragment == target_)
- has_entered_target_ = true;
- ancestors_precede_in_preorder_.push_back(!has_entered_target_);
- return true;
+ Vector<Result> CollectFrom(const NGPhysicalFragment& fragment) final {
+ // TODO(xiaochengh): Change this into CollectInclusivlyFrom() to include
+ // subtree root to align with NodeTraversal::AncestorsOf().
+ return CollectExclusivelyFrom(fragment);
}
- bool RemoveOnExit(const NGPhysicalFragment* fragment) {
- if (fragment != target_) {
- const bool precedes_in_preorder = ancestors_precede_in_preorder_.back();
- ancestors_precede_in_preorder_.pop_back();
- return !precedes_in_preorder || !has_exited_target_;
+ private:
+ void Visit() final {
+ if (&GetFragment() == &target_) {
+ SetShouldStopTraversing();
+ return;
}
- has_exited_target_ = true;
- ancestors_precede_in_preorder_.pop_back();
- return false;
+
+ VisitChildren();
+ if (HasStoppedTraversing())
+ Emit();
+ }
+
+ const NGPhysicalFragment& target_;
+};
+
+// The visitor emitting inclusive ancestors of the given fragment in bottom-up
+// order.
+class InclusiveAncestorCollector : public NGPhysicalFragmentCollectorBase {
+ STACK_ALLOCATED();
+
+ public:
+ explicit InclusiveAncestorCollector(const NGPhysicalFragment& target)
+ : target_(target) {}
+
+ Vector<Result> CollectFrom(const NGPhysicalFragment& fragment) final {
+ // TODO(xiaochengh): Change this into CollectInclusivlyFrom() to include
+ // subtree root to align with NodeTraversal::InclusiveAncestorsOf().
+ return CollectExclusivelyFrom(fragment);
}
private:
- const NGPhysicalFragment* target_;
+ void Visit() final {
+ if (&GetFragment() == &target_) {
+ SetShouldStopTraversing();
+ Emit();
+ return;
+ }
- bool has_entered_target_ = false;
- bool has_exited_target_ = false;
+ VisitChildren();
+ if (HasStoppedTraversing())
+ Emit();
+ }
- // For each currently entered but not-yet-exited fragment, stores a boolean of
- // whether it precedes |target_| in preorder.
- Vector<bool> ancestors_precede_in_preorder_;
+ const NGPhysicalFragment& target_;
};
} // namespace
// static
-Vector<Result, 1> NGInlineFragmentTraversal::SelfFragmentsOf(
+Vector<Result> NGInlineFragmentTraversal::SelfFragmentsOf(
const NGPhysicalContainerFragment& container,
const LayoutObject* layout_object) {
if (layout_object->IsLayoutInline()) {
- LayoutInlineFilter filter(*ToLayoutInline(layout_object));
- Vector<Result, 1> results;
- CollectInlineFragments(container, {}, filter, &results);
- return results;
- }
- LayoutObjectFilter filter(layout_object);
- Vector<Result, 1> results;
- CollectInlineFragments(container, {}, filter, &results);
- return results;
+ return LayoutInlineCollector(ToLayoutInline(*layout_object))
+ .CollectFrom(container);
+ }
+ return LayoutObjectCollector(layout_object).CollectFrom(container);
}
// static
Vector<Result> NGInlineFragmentTraversal::DescendantsOf(
const NGPhysicalContainerFragment& container) {
- AddAllFilter add_all;
- Vector<Result> results;
- CollectInlineFragments(container, {}, add_all, &results);
- return results;
+ return DescendantCollector().CollectFrom(container);
}
// static
Vector<Result> NGInlineFragmentTraversal::InclusiveDescendantsOf(
const NGPhysicalFragment& root) {
- Vector<Result> results =
- root.IsContainer() ? DescendantsOf(ToNGPhysicalContainerFragment(root))
- : Vector<Result>();
- results.push_front(Result{&root, {}});
- return results;
+ return InclusiveDescendantCollector().CollectFrom(root);
}
// static
Vector<Result> NGInlineFragmentTraversal::InclusiveAncestorsOf(
const NGPhysicalContainerFragment& container,
const NGPhysicalFragment& target) {
- InclusiveAncestorFilter inclusive_ancestors_of(target);
- Vector<Result> results;
- CollectInlineFragments(container, {}, inclusive_ancestors_of, &results);
- std::reverse(results.begin(), results.end());
- return results;
+ return InclusiveAncestorCollector(target).CollectFrom(container);
}
// static
Vector<Result> NGInlineFragmentTraversal::AncestorsOf(
const NGPhysicalContainerFragment& container,
const NGPhysicalFragment& target) {
- Vector<Result> results = InclusiveAncestorsOf(container, target);
- DCHECK(results.size());
- DCHECK_EQ(results.front().fragment, &target);
- results.erase(results.begin());
- return results;
+ return AncestorCollector(target).CollectFrom(container);
}
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h
index 27fb2e44c87..ef8bfd61034 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h
@@ -44,7 +44,7 @@ class CORE_EXPORT NGInlineFragmentTraversal {
// Returns list of inline fragments produced from the specified LayoutObject.
// The search is restricted in the subtree of |container|.
- static Vector<NGPhysicalFragmentWithOffset, 1> SelfFragmentsOf(
+ static Vector<NGPhysicalFragmentWithOffset> SelfFragmentsOf(
const NGPhysicalContainerFragment& container,
const LayoutObject* target);
};
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc
index 40bf8c8431a..822aca55e9f 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc
@@ -106,10 +106,7 @@ TEST_F(NGInlineFragmentTraversalTest, SelfFragmentsOf) {
// <b> generates two box fragments since its content is in two lines.
EXPECT_NEXT_BOX(iter, "filter");
- EXPECT_NEXT_TEXT(iter, "bar");
- EXPECT_NEXT_TEXT(iter, "\n");
EXPECT_NEXT_BOX(iter, "filter");
- EXPECT_NEXT_TEXT(iter, "baz");
EXPECT_EQ(iter, descendants.end());
}
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc
index 8e5a6288ddf..4ae097be73a 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc
@@ -37,6 +37,19 @@ bool IsInlineBoxEmpty(const ComputedStyle& style,
return true;
}
+// TODO(xiaochengh): Deduplicate with a similar function in ng_paint_fragment.cc
+// ::before, ::after and ::first-letter can be hit test targets.
+bool CanBeHitTestTargetPseudoNodeStyle(const ComputedStyle& style) {
+ switch (style.StyleType()) {
+ case kPseudoIdBefore:
+ case kPseudoIdAfter:
+ case kPseudoIdFirstLetter:
+ return true;
+ default:
+ return false;
+ }
+}
+
} // namespace
NGInlineItem::NGInlineItem(NGInlineItemType type,
@@ -47,17 +60,20 @@ NGInlineItem::NGInlineItem(NGInlineItemType type,
bool end_may_collapse)
: start_offset_(start),
end_offset_(end),
- script_(USCRIPT_INVALID_CODE),
style_(style),
layout_object_(layout_object),
type_(type),
+ script_(0),
+ font_fallback_priority_(0),
+ render_orientation_(0),
bidi_level_(UBIDI_LTR),
shape_options_(kPreContext | kPostContext),
is_empty_item_(false),
should_create_box_fragment_(false),
style_variant_(static_cast<unsigned>(NGStyleVariant::kStandard)),
end_collapse_type_(kNotCollapsible),
- end_may_collapse_(end_may_collapse) {
+ end_may_collapse_(end_may_collapse),
+ is_symbol_marker_(false) {
DCHECK_GE(end, start);
ComputeBoxProperties();
}
@@ -68,18 +84,21 @@ NGInlineItem::NGInlineItem(const NGInlineItem& other,
scoped_refptr<const ShapeResult> shape_result)
: start_offset_(start),
end_offset_(end),
- script_(other.script_),
shape_result_(shape_result),
style_(other.style_),
layout_object_(other.layout_object_),
type_(other.type_),
+ script_(other.script_),
+ font_fallback_priority_(other.font_fallback_priority_),
+ render_orientation_(other.render_orientation_),
bidi_level_(other.bidi_level_),
shape_options_(other.shape_options_),
is_empty_item_(other.is_empty_item_),
should_create_box_fragment_(other.should_create_box_fragment_),
style_variant_(other.style_variant_),
end_collapse_type_(other.end_collapse_type_),
- end_may_collapse_(other.end_may_collapse_) {
+ end_may_collapse_(other.end_may_collapse_),
+ is_symbol_marker_(other.is_symbol_marker_) {
DCHECK_GE(end, start);
}
@@ -95,7 +114,7 @@ void NGInlineItem::ComputeBoxProperties() {
if (type_ == NGInlineItem::kOpenTag) {
DCHECK(style_ && layout_object_ && layout_object_->IsLayoutInline());
- if (layout_object_->HasBoxDecorationBackground() || style_->HasPadding() ||
+ if (style_->HasBoxDecorationBackground() || style_->HasPadding() ||
style_->HasMargin()) {
is_empty_item_ = IsInlineBoxEmpty(*style_, *layout_object_);
should_create_box_fragment_ = true;
@@ -104,7 +123,10 @@ void NGInlineItem::ComputeBoxProperties() {
should_create_box_fragment_ =
ToLayoutBoxModelObject(layout_object_)->HasSelfPaintingLayer() ||
style_->HasOutline() || style_->CanContainAbsolutePositionObjects() ||
- style_->CanContainFixedPositionObjects(false);
+ style_->CanContainFixedPositionObjects(false) ||
+ ToLayoutBoxModelObject(layout_object_)
+ ->ShouldApplyPaintContainment() ||
+ CanBeHitTestTargetPseudoNodeStyle(*style_);
}
return;
}
@@ -121,6 +143,96 @@ const char* NGInlineItem::NGInlineItemTypeToString(int val) const {
return kNGInlineItemTypeStrings[val];
}
+UScriptCode NGInlineItem::Script() const {
+ return script_ != kInvalidScript ? static_cast<UScriptCode>(script_)
+ : USCRIPT_INVALID_CODE;
+}
+
+FontFallbackPriority NGInlineItem::GetFontFallbackPriority() const {
+ return static_cast<enum FontFallbackPriority>(font_fallback_priority_);
+}
+
+OrientationIterator::RenderOrientation NGInlineItem::RenderOrientation() const {
+ return static_cast<OrientationIterator::RenderOrientation>(
+ render_orientation_);
+}
+
+RunSegmenter::RunSegmenterRange NGInlineItem::CreateRunSegmenterRange() const {
+ return {start_offset_, end_offset_, Script(), RenderOrientation(),
+ GetFontFallbackPriority()};
+}
+
+bool NGInlineItem::EqualsRunSegment(const NGInlineItem& other) const {
+ return script_ == other.script_ &&
+ font_fallback_priority_ == other.font_fallback_priority_ &&
+ render_orientation_ == other.render_orientation_;
+}
+
+void NGInlineItem::SetRunSegment(const RunSegmenter::RunSegmenterRange& range) {
+ DCHECK_EQ(Type(), NGInlineItem::kText);
+
+ // Orientation should be set in a separate pass. See
+ // NGInlineNode::SegmentScriptRuns().
+ DCHECK_EQ(range.render_orientation, OrientationIterator::kOrientationKeep);
+
+ script_ = static_cast<unsigned>(range.script);
+ font_fallback_priority_ = static_cast<unsigned>(range.font_fallback_priority);
+
+ // Ensure our bit fields are large enough by reading them back.
+ DCHECK_EQ(range.script, Script());
+ DCHECK_EQ(range.font_fallback_priority, GetFontFallbackPriority());
+}
+
+void NGInlineItem::SetFontOrientation(
+ OrientationIterator::RenderOrientation orientation) {
+ DCHECK_EQ(Type(), NGInlineItem::kText);
+
+ // Ensure the value can fit in the bit field.
+ DCHECK_LT(static_cast<unsigned>(orientation), 1u << 1);
+
+ render_orientation_ = orientation != 0;
+}
+
+unsigned NGInlineItem::PopulateItemsFromRun(
+ Vector<NGInlineItem>& items,
+ unsigned index,
+ const RunSegmenter::RunSegmenterRange& range) {
+ DCHECK_GE(range.end, items[index].start_offset_);
+
+ for (;; index++) {
+ NGInlineItem& item = items[index];
+ DCHECK_LE(item.start_offset_, range.end);
+
+ if (item.Type() == NGInlineItem::kText)
+ item.SetRunSegment(range);
+
+ if (range.end == item.end_offset_)
+ break;
+ if (range.end < item.end_offset_) {
+ Split(items, index, range.end);
+ break;
+ }
+ }
+ return index + 1;
+}
+
+unsigned NGInlineItem::PopulateItemsFromFontOrientation(
+ Vector<NGInlineItem>& items,
+ unsigned index,
+ unsigned end_offset,
+ OrientationIterator::RenderOrientation orientation) {
+ // FontOrientaiton is set per item, end_offset should be within this item.
+ NGInlineItem& item = items[index];
+ DCHECK_GE(end_offset, item.start_offset_);
+ DCHECK_LE(end_offset, item.end_offset_);
+
+ item.SetFontOrientation(orientation);
+
+ if (end_offset < item.end_offset_)
+ Split(items, index, end_offset);
+ return index + 1;
+}
+
void NGInlineItem::SetBidiLevel(UBiDiLevel level) {
// Invalidate ShapeResult because it depends on the resolved direction.
if (DirectionFromLevel(level) != DirectionFromLevel(bidi_level_))
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h
index 1b40a3a53d7..2e2a046614a 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h
@@ -9,6 +9,7 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
#include "third_party/blink/renderer/core/layout/ng/ng_style_variant.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/run_segmenter.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
#include "third_party/blink/renderer/platform/text/text_direction.h"
@@ -77,7 +78,9 @@ class CORE_EXPORT NGInlineItem {
NGInlineItemType Type() const { return static_cast<NGInlineItemType>(type_); }
const char* NGInlineItemTypeToString(int val) const;
- const ShapeResult* TextShapeResult() const { return shape_result_.get(); }
+ scoped_refptr<const ShapeResult> TextShapeResult() const {
+ return shape_result_;
+ }
NGLayoutInlineShapeOptions ShapeOptions() const {
return static_cast<NGLayoutInlineShapeOptions>(shape_options_);
}
@@ -100,7 +103,6 @@ class CORE_EXPORT NGInlineItem {
// direction.
UBiDiLevel BidiLevelForReorder() const;
- UScriptCode GetScript() const { return script_; }
const ComputedStyle* Style() const { return style_.get(); }
LayoutObject* GetLayoutObject() const { return layout_object_; }
@@ -129,6 +131,26 @@ class CORE_EXPORT NGInlineItem {
bool EndMayCollapse() const { return end_may_collapse_; }
static void Split(Vector<NGInlineItem>&, unsigned index, unsigned offset);
+
+ // Get RunSegmenter properties.
+ UScriptCode Script() const;
+ FontFallbackPriority GetFontFallbackPriority() const;
+ OrientationIterator::RenderOrientation RenderOrientation() const;
+ RunSegmenter::RunSegmenterRange CreateRunSegmenterRange() const;
+ // Whether the other item has the same RunSegmenter properties or not.
+ bool EqualsRunSegment(const NGInlineItem&) const;
+ // Set RunSegmenter properties.
+ static unsigned PopulateItemsFromRun(Vector<NGInlineItem>&,
+ unsigned index,
+ const RunSegmenter::RunSegmenterRange&);
+ void SetRunSegment(const RunSegmenter::RunSegmenterRange&);
+ static unsigned PopulateItemsFromFontOrientation(
+ Vector<NGInlineItem>&,
+ unsigned index,
+ unsigned end_offset,
+ OrientationIterator::RenderOrientation);
+ void SetFontOrientation(OrientationIterator::RenderOrientation);
+
void SetBidiLevel(UBiDiLevel);
static unsigned SetBidiLevel(Vector<NGInlineItem>&,
unsigned index,
@@ -138,6 +160,9 @@ class CORE_EXPORT NGInlineItem {
void AssertOffset(unsigned offset) const;
void AssertEndOffset(unsigned offset) const;
+ bool IsSymbolMarker() const { return is_symbol_marker_; }
+ void SetIsSymbolMarker(bool b) { is_symbol_marker_ = b; }
+
String ToString() const;
private:
@@ -145,19 +170,27 @@ class CORE_EXPORT NGInlineItem {
unsigned start_offset_;
unsigned end_offset_;
- UScriptCode script_;
scoped_refptr<const ShapeResult> shape_result_;
scoped_refptr<const ComputedStyle> style_;
LayoutObject* layout_object_;
+ // UScriptCode is -1 (USCRIPT_INVALID_CODE) to 177 as of ICU 60.
+ // This can be packed to 8 bits, by handling -1 separately.
+ static constexpr unsigned kScriptBits = 8;
+ static constexpr unsigned kInvalidScript = (1 << kScriptBits) - 1;
+
unsigned type_ : 4;
- unsigned bidi_level_ : 8; // UBiDiLevel is defined as uint8_t.
+ unsigned script_ : kScriptBits;
+ unsigned font_fallback_priority_ : 2; // FontFallbackPriority.
+ unsigned render_orientation_ : 1; // RenderOrientation (excl. kInvalid.)
+ unsigned bidi_level_ : 8; // UBiDiLevel is defined as uint8_t.
unsigned shape_options_ : 2;
unsigned is_empty_item_ : 1;
unsigned should_create_box_fragment_ : 1;
unsigned style_variant_ : 2;
unsigned end_collapse_type_ : 2; // NGCollapseType
unsigned end_may_collapse_ : 1;
+ unsigned is_symbol_marker_ : 1;
friend class NGInlineNode;
};
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc
index 43cdd7b803a..50ee0dee064 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc
@@ -15,8 +15,13 @@ NGInlineItemResult::NGInlineItemResult()
NGInlineItemResult::NGInlineItemResult(const NGInlineItem* item,
unsigned index,
unsigned start,
- unsigned end)
- : item(item), item_index(index), start_offset(start), end_offset(end) {}
+ unsigned end,
+ bool should_create_line_box)
+ : item(item),
+ item_index(index),
+ start_offset(start),
+ end_offset(end),
+ should_create_line_box(should_create_line_box) {}
void NGLineInfo::SetLineStyle(const NGInlineNode& node,
const NGInlineItemsData& items_data,
@@ -26,7 +31,7 @@ void NGLineInfo::SetLineStyle(const NGInlineNode& node,
bool is_after_forced_break) {
use_first_line_style_ = use_first_line_style;
items_data_ = &items_data;
- line_style_ = node.GetLayoutObject()->Style(use_first_line_style_);
+ line_style_ = node.GetLayoutBox()->Style(use_first_line_style_);
if (line_style_->ShouldUseTextIndent(is_first_line, is_after_forced_break)) {
// 'text-indent' applies to block container, and percentage is of its
@@ -47,11 +52,13 @@ void NGLineInfo::SetLineStyle(const NGInlineNode& node,
}
#if DCHECK_IS_ON()
-void NGInlineItemResult::CheckConsistency() const {
+void NGInlineItemResult::CheckConsistency(bool during_line_break) const {
DCHECK(item);
if (item->Type() == NGInlineItem::kText) {
- DCHECK(shape_result);
DCHECK_LT(start_offset, end_offset);
+ if (during_line_break && !shape_result)
+ return;
+ DCHECK(shape_result);
DCHECK_EQ(end_offset - start_offset, shape_result->NumCharacters());
DCHECK_EQ(start_offset, shape_result->StartIndexForResult());
DCHECK_EQ(end_offset, shape_result->EndIndexForResult());
@@ -59,6 +66,20 @@ void NGInlineItemResult::CheckConsistency() const {
}
#endif
+LayoutUnit NGLineInfo::ComputeWidth() const {
+ LayoutUnit inline_size = TextIndent();
+ for (const NGInlineItemResult& item_result : Results())
+ inline_size += item_result.inline_size;
+
+ if (UNLIKELY(line_end_fragment_)) {
+ inline_size += line_end_fragment_->Size()
+ .ConvertToLogical(LineStyle().GetWritingMode())
+ .inline_size;
+ }
+
+ return inline_size;
+}
+
void NGLineInfo::SetLineBfcOffset(NGBfcOffset line_bfc_offset,
LayoutUnit available_width,
LayoutUnit width) {
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h
index 514ae11066d..70ecc25ca16 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h
@@ -51,12 +51,12 @@ struct CORE_EXPORT NGInlineItemResult {
scoped_refptr<NGLayoutResult> layout_result;
// Margins and padding for atomic inline items and open/close tags.
- NGBoxStrut margins;
- NGBoxStrut padding;
+ NGLineBoxStrut margins;
+ NGLineBoxStrut padding;
// Borders/padding for open tags.
- LayoutUnit borders_paddings_block_start;
- LayoutUnit borders_paddings_block_end;
+ LayoutUnit borders_paddings_line_over;
+ LayoutUnit borders_paddings_line_under;
// Has start/end edge for open/close tags.
bool has_edge = false;
@@ -78,6 +78,22 @@ struct CORE_EXPORT NGInlineItemResult {
// characters.
bool has_only_trailing_spaces = false;
+ // We don't create "certain zero-height line boxes".
+ // https://drafts.csswg.org/css2/visuren.html#phantom-line-box
+ // Such line boxes do not prevent two margins being "adjoining", and thus
+ // collapsing.
+ // https://drafts.csswg.org/css2/box.html#collapsing-margins
+ //
+ // This field should be initialized to the previous value in the
+ // NGInlineItemResults list. If line breaker rewinds NGInlineItemResults
+ // list, we can still look at the last value in the list to determine if we
+ // need a line box. E.g.
+ // [float should_create_line_box: false], [text should_create_line_box: true]
+ //
+ // If "text" doesn't fit, and we rewind so that we only have "float", we can
+ // correctly determine that we don't need a line box.
+ bool should_create_line_box = false;
+
// End effects for text items.
// The effects are included in |shape_result|, but not in text content.
NGTextEndEffect text_end_effect = NGTextEndEffect::kNone;
@@ -86,10 +102,11 @@ struct CORE_EXPORT NGInlineItemResult {
NGInlineItemResult(const NGInlineItem*,
unsigned index,
unsigned start,
- unsigned end);
+ unsigned end,
+ bool should_create_line_box);
#if DCHECK_IS_ON()
- void CheckConsistency() const;
+ void CheckConsistency(bool during_line_break = false) const;
#endif
};
@@ -105,9 +122,6 @@ class CORE_EXPORT NGLineInfo {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
- NGLineInfo() = default;
- explicit NGLineInfo(size_t capacity) : results_(capacity) {}
-
const NGInlineItemsData& ItemsData() const {
DCHECK(items_data_);
return *items_data_;
@@ -137,8 +151,14 @@ class CORE_EXPORT NGLineInfo {
bool IsLastLine() const { return is_last_line_; }
void SetIsLastLine(bool is_last_line) { is_last_line_ = is_last_line; }
+ // If the line is marked as empty, it means that there's no content that
+ // requires it to be present at all, e.g. when there are only close tags with
+ // no margin/border/padding.
+ bool IsEmptyLine() const { return is_empty_line_; }
+ void SetIsEmptyLine() { is_empty_line_ = true; }
+
// NGInlineItemResults for this line.
- NGInlineItemResults& Results() { return results_; }
+ NGInlineItemResults* MutableResults() { return &results_; }
const NGInlineItemResults& Results() const { return results_; }
LayoutUnit TextIndent() const { return text_indent_; }
@@ -146,6 +166,7 @@ class CORE_EXPORT NGLineInfo {
NGBfcOffset LineBfcOffset() const { return line_bfc_offset_; }
LayoutUnit AvailableWidth() const { return available_width_; }
LayoutUnit Width() const { return width_; }
+ LayoutUnit ComputeWidth() const;
void SetLineBfcOffset(NGBfcOffset line_bfc_offset,
LayoutUnit available_width,
LayoutUnit width);
@@ -183,6 +204,7 @@ class CORE_EXPORT NGLineInfo {
bool use_first_line_style_ = false;
bool is_last_line_ = false;
+ bool is_empty_line_ = false;
};
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
index 7fad403e72e..ae1cb609e0a 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
@@ -95,19 +95,21 @@ bool ShouldRemoveNewlineSlow(const StringBuilder& before,
if (before.Is8Bit() || after.Is8Bit())
return false;
- // Remove if East Asian Widths of both before/after the newline are Wide.
+ // Remove if East Asian Widths of both before/after the newline are Wide, and
+ // neither side is Hangul.
+ // TODO(layout-dev): Don't remove if any side is Emoji.
if (U16_IS_TRAIL(last) && space_index >= 2) {
UChar last_last = before[space_index - 2];
if (U16_IS_LEAD(last_last))
last = U16_GET_SUPPLEMENTARY(last_last, last);
}
- if (IsEastAsianWidthWide(last, before_style)) {
+ if (!Character::IsHangul(last) && IsEastAsianWidthWide(last, before_style)) {
if (U16_IS_LEAD(next) && after.length() > 1) {
UChar next_next = after[1];
if (U16_IS_TRAIL(next_next))
next = U16_GET_SUPPLEMENTARY(next, next_next);
}
- if (IsEastAsianWidthWide(next, after_style))
+ if (!Character::IsHangul(next) && IsEastAsianWidthWide(next, after_style))
return true;
}
@@ -799,6 +801,13 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::Exit(
}
}
+template <typename OffsetMappingBuilder>
+void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::SetIsSymbolMarker(
+ bool b) {
+ DCHECK(!items_->IsEmpty());
+ items_->back().SetIsSymbolMarker(b);
+}
+
template class CORE_TEMPLATE_EXPORT
NGInlineItemsBuilderTemplate<EmptyOffsetMappingBuilder>;
template class CORE_TEMPLATE_EXPORT
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
index 53e34b6cef3..e944f9ac38f 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
@@ -36,7 +36,7 @@ class LayoutText;
// offsets in |text_|.
// See https://goo.gl/CJbxky for more details about offset mapping.
template <typename OffsetMappingBuilder>
-class CORE_TEMPLATE_CLASS_EXPORT NGInlineItemsBuilderTemplate {
+class NGInlineItemsBuilderTemplate {
STACK_ALLOCATED();
public:
@@ -107,6 +107,8 @@ class CORE_TEMPLATE_CLASS_EXPORT NGInlineItemsBuilderTemplate {
OffsetMappingBuilder& GetOffsetMappingBuilder() { return mapping_builder_; }
+ void SetIsSymbolMarker(bool b);
+
private:
Vector<NGInlineItem>* items_;
StringBuilder text_;
@@ -155,10 +157,11 @@ class CORE_TEMPLATE_CLASS_EXPORT NGInlineItemsBuilderTemplate {
};
template <>
-String NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::ToString();
+CORE_EXPORT String
+NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::ToString();
template <>
-bool NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::Append(
+CORE_EXPORT bool NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::Append(
const String&,
LayoutObject*,
const Vector<NGInlineItem*>&);
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
index ae5a273962c..1d75a511be4 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
@@ -68,10 +68,13 @@ class NGInlineItemsBuilderTest : public PageTestBase {
const String& TestAppend(Vector<Input> inputs) {
items_.clear();
+ Vector<LayoutText*> anonymous_objects;
NGInlineItemsBuilderForOffsetMapping builder(&items_);
for (Input& input : inputs) {
- if (!input.layout_text)
+ if (!input.layout_text) {
input.layout_text = LayoutText::CreateEmptyAnonymous(GetDocument());
+ anonymous_objects.push_back(input.layout_text);
+ }
builder.Append(input.text, GetStyle(input.whitespace).get(),
input.layout_text);
}
@@ -79,6 +82,8 @@ class NGInlineItemsBuilderTest : public PageTestBase {
collapsed_ = GetCollapsed(builder.GetOffsetMappingBuilder());
ValidateItems();
CheckReuseItemsProducesSameResult(inputs);
+ for (LayoutObject* anonymous_object : anonymous_objects)
+ anonymous_object->Destroy();
return text_;
}
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
index 0d04530a405..303800dd4db 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
@@ -50,20 +50,31 @@ struct NGLineAlign {
NGLineAlign::NGLineAlign(const NGLineInfo& line_info) {
space = line_info.AvailableWidth() - line_info.Width();
- // Eliminate trailing spaces from the alignment space.
+ // Compute the end text offset of this line for the alignment purpose.
+ // Trailing spaces are not part of the alignment space even when they are
+ // preserved.
const NGInlineItemResults& item_results = line_info.Results();
for (auto it = item_results.rbegin(); it != item_results.rend(); ++it) {
const NGInlineItemResult& item_result = *it;
- if (!item_result.has_only_trailing_spaces) {
- end_offset = item_result.end_offset;
- space += trailing_spaces_width;
- return;
+
+ // If this item is opaque to whitespace collapsing, whitespace before this
+ // item maybe collapsed. Keep looking for previous items.
+ if (item_result.item && item_result.item->EndCollapseType() ==
+ NGInlineItem::kOpaqueToCollapsing) {
+ continue;
+ }
+
+ if (item_result.has_only_trailing_spaces) {
+ trailing_spaces_width += item_result.inline_size;
+ continue;
}
- trailing_spaces_width += item_result.inline_size;
+
+ end_offset = item_result.end_offset;
+ space += trailing_spaces_width;
+ return;
}
// An empty line, or only trailing spaces.
- DCHECK_EQ(space, line_info.AvailableWidth() - line_info.TextIndent());
end_offset = line_info.StartOffset();
space += trailing_spaces_width;
}
@@ -153,7 +164,8 @@ void NGInlineLayoutAlgorithm::PrepareBoxStates(
void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info,
NGExclusionSpace* exclusion_space) {
- NGInlineItemResults* line_items = &line_info->Results();
+ // Needs MutableResults to move ShapeResult out of the NGLineInfo.
+ NGInlineItemResults* line_items = line_info->MutableResults();
line_box_.clear();
// Apply justification before placing items, because it affects size/position
@@ -177,7 +189,13 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info,
NGInlineBoxState* box =
box_states_->OnBeginPlaceItems(&line_style, baseline_type_, quirks_mode_);
- for (auto& item_result : *line_items) {
+ // In order to match other browsers when list-style-type: none, pretend
+ // there's an invisible marker here.
+ if (line_style.Display() == EDisplay::kListItem &&
+ line_style.ListStyleType() == EListStyleType::kNone)
+ box->ComputeTextMetrics(line_style, baseline_type_);
+
+ for (NGInlineItemResult& item_result : *line_items) {
DCHECK(item_result.item);
const NGInlineItem& item = *item_result.item;
if (item.Type() == NGInlineItem::kText) {
@@ -195,9 +213,15 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info,
baseline_type_);
}
- text_builder.SetItem(NGPhysicalTextFragment::kNormalText,
- line_info->ItemsData(), &item_result,
- box->text_height);
+ if (item.IsSymbolMarker()) {
+ text_builder.SetItem(NGPhysicalTextFragment::kSymbolMarker,
+ line_info->ItemsData(), &item_result,
+ box->text_height);
+ } else {
+ text_builder.SetItem(NGPhysicalTextFragment::kNormalText,
+ line_info->ItemsData(), &item_result,
+ box->text_height);
+ }
line_box_.AddChild(text_builder.ToTextFragment(), box->text_top,
item_result.inline_size, item.BidiLevel());
} else if (item.Type() == NGInlineItem::kControl) {
@@ -234,9 +258,10 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info,
IsLtr(line_info->BaseDirection()) ? 0 : 1, box);
}
- if (line_box_.IsEmpty() && !container_builder_.UnpositionedListMarker()) {
- return; // The line was empty.
- }
+ // We can return early if we don't have any children (and don't need to
+ // create a line-box for a list marker, etc).
+ if (line_box_.IsEmpty() && line_info->IsEmptyLine())
+ return;
box_states_->OnEndPlaceItems(&line_box_, baseline_type_);
@@ -267,20 +292,17 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info,
// Handle out-of-flow positioned objects. They need inline offsets for their
// static positions.
- if (!PlaceOutOfFlowObjects(*line_info, line_box_metrics) &&
- !container_builder_.UnpositionedListMarker()) {
- // If we have out-of-flow objects but nothing else, we don't have line box
- // metrics nor BFC offset. Exit early.
+ PlaceOutOfFlowObjects(*line_info, line_box_metrics);
+
+ // Even if we have something in-flow, it may just be empty items that
+ // shouldn't trigger creation of a line. Exit now if that's the case.
+ if (line_info->IsEmptyLine())
return;
- }
DCHECK(!line_box_metrics.IsEmpty());
NGBfcOffset line_bfc_offset(line_info->LineBfcOffset());
- // TODO(kojii): Implement flipped line (vertical-lr). In this case, line_top
- // and block_start do not match.
-
// Up until this point, children are placed so that the dominant baseline is
// at 0. Move them to the final baseline position, and set the logical top of
// the line box to the line top.
@@ -411,7 +433,7 @@ void NGInlineLayoutAlgorithm::PlaceLayoutResult(NGInlineItemResult* item_result,
if (box)
box->metrics.Unite(metrics);
- LayoutUnit line_top = item_result->margins.block_start - metrics.ascent;
+ LayoutUnit line_top = item_result->margins.line_over - metrics.ascent;
line_box_.AddChild(std::move(item_result->layout_result),
NGLogicalOffset{inline_offset, line_top},
item_result->inline_size, item.BidiLevel());
@@ -419,21 +441,29 @@ void NGInlineLayoutAlgorithm::PlaceLayoutResult(NGInlineItemResult* item_result,
// Place all out-of-flow objects in |line_box_| and clear them.
// @return whether |line_box_| has any in-flow fragments.
-bool NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects(
+void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects(
const NGLineInfo& line_info,
const NGLineHeightMetrics& line_box_metrics) {
- bool has_fragments = false;
- for (auto& child : line_box_) {
+ for (NGLineBoxFragmentBuilder::Child& child : line_box_) {
if (LayoutObject* box = child.out_of_flow_positioned_box) {
// The static position is at the line-top. Ignore the block_offset.
NGLogicalOffset static_offset(child.offset.inline_offset, LayoutUnit());
// If a block-level box appears in the middle of a line, move the static
// position to where the next block will be placed.
- if (static_offset.inline_offset &&
- !box->StyleRef().IsOriginalDisplayInlineType()) {
- static_offset.inline_offset = LayoutUnit();
- if (!line_box_metrics.IsEmpty())
+ if (!box->StyleRef().IsOriginalDisplayInlineType()) {
+ LayoutUnit line_offset;
+ if (!line_info.IsEmptyLine()) {
+ line_offset = line_info.LineBfcOffset().line_offset -
+ ConstraintSpace().BfcOffset().line_offset;
+ }
+ line_offset += line_info.TextIndent();
+
+ // We need to subtract the line offset, in order to ignore
+ // floats and text-indent.
+ static_offset.inline_offset = -line_offset;
+
+ if (child.offset.inline_offset && !line_box_metrics.IsEmpty())
static_offset.block_offset = line_box_metrics.LineHeight();
}
@@ -443,11 +473,8 @@ bool NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects(
child.out_of_flow_positioned_box = child.out_of_flow_containing_box =
nullptr;
- } else if (!has_fragments) {
- has_fragments = child.HasFragment();
}
}
- return has_fragments;
}
// Place a list marker.
@@ -490,7 +517,7 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(NGLineInfo* line_info) {
if (!spacing.HasExpansion())
return false; // no expansion opportunities exist.
- for (NGInlineItemResult& item_result : line_info->Results()) {
+ for (NGInlineItemResult& item_result : *line_info->MutableResults()) {
if (item_result.has_only_trailing_spaces)
break;
if (item_result.shape_result) {
@@ -508,8 +535,16 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(NGLineInfo* line_info) {
shape_result->StartIndexForResult());
item_result.inline_size = shape_result->SnappedWidth();
item_result.shape_result = std::move(shape_result);
- } else {
- // TODO(kojii): Implement atomic inline.
+ } else if (item_result.item->Type() == NGInlineItem::kAtomicInline) {
+ float offset = 0.f;
+ DCHECK_LE(line_info->StartOffset(), item_result.start_offset);
+ unsigned line_text_offset =
+ item_result.start_offset - line_info->StartOffset();
+ DCHECK_EQ(kObjectReplacementCharacter, line_text[line_text_offset]);
+ float space = spacing.ComputeSpacing(line_text_offset, offset);
+ item_result.inline_size += space;
+ // |offset| is non-zero only before CJK characters.
+ DCHECK_EQ(offset, 0.f);
}
}
return true;
@@ -564,12 +599,21 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
bool is_empty_inline = Node().IsEmptyInline();
if (is_empty_inline) {
+ // Margins should collapse across "certain zero-height line boxes".
+ // https://drafts.csswg.org/css2/box.html#collapsing-margins
+ container_builder_.SetEndMarginStrut(ConstraintSpace().MarginStrut());
+
// We're just going to collapse through this one, so whatever went in on one
// side will go out on the other side. The position of the adjoining floats
// will be affected by any subsequent block, until the BFC offset is
// resolved.
container_builder_.AddAdjoiningFloatTypes(
ConstraintSpace().AdjoiningFloatTypes());
+
+ // For the empty lines, most of the logic here are not necessary, but in
+ // some edge cases we still need to create box fragments, such as when it
+ // has a containing block for out of flow objects. For now, use the code
+ // path than to create a fast code path for the stability.
} else {
DCHECK(ConstraintSpace().MarginStrut().IsEmpty());
container_builder_.SetBfcOffset(ConstraintSpace().BfcOffset());
@@ -584,26 +628,6 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
unsigned handled_item_index =
PositionLeadingItems(initial_exclusion_space.get());
- // If we are an empty inline, we don't have to run the full algorithm, we can
- // return now as we should have positioned all of our floats.
- if (is_empty_inline) {
- DCHECK_EQ(handled_item_index, Node().ItemsData(false).items.size());
-
- container_builder_.SwapPositionedFloats(&positioned_floats_);
- container_builder_.SetEndMarginStrut(ConstraintSpace().MarginStrut());
- container_builder_.SetExclusionSpace(std::move(initial_exclusion_space));
-
- Vector<NGOutOfFlowPositionedDescendant> descendant_candidates;
- container_builder_.GetAndClearOutOfFlowDescendantCandidates(
- &descendant_candidates, nullptr);
- for (auto& descendant : descendant_candidates)
- container_builder_.AddOutOfFlowDescendant(descendant);
-
- return container_builder_.ToLineBoxFragment();
- }
-
- DCHECK(container_builder_.BfcOffset());
-
// We query all the layout opportunities on the initial exclusion space up
// front, as if the line breaker may add floats and change the opportunities.
const Vector<NGLayoutOpportunity> opportunities =
@@ -612,7 +636,8 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
ConstraintSpace().AvailableSize().inline_size);
Vector<NGPositionedFloat> positioned_floats;
- DCHECK(unpositioned_floats_.IsEmpty());
+ // We shouldn't have any unpositioned floats if we aren't empty.
+ DCHECK(unpositioned_floats_.IsEmpty() || is_empty_inline);
std::unique_ptr<NGExclusionSpace> exclusion_space;
NGInlineBreakToken* break_token = BreakToken();
@@ -653,15 +678,12 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
line_block_size, block_delta);
NGLineInfo line_info;
- NGLineBreaker line_breaker(
- Node(), NGLineBreakerMode::kContent, constraint_space_,
- &positioned_floats, &unpositioned_floats_, &container_builder_,
- exclusion_space.get(), handled_item_index, break_token);
-
- // TODO(ikilpatrick): Does this always succeed when we aren't an empty
- // inline?
- if (!line_breaker.NextLine(line_opportunity, &line_info))
- break;
+ NGLineBreaker line_breaker(Node(), NGLineBreakerMode::kContent,
+ constraint_space_, &positioned_floats,
+ &unpositioned_floats_, &container_builder_,
+ exclusion_space.get(), handled_item_index,
+ line_opportunity, break_token);
+ line_breaker.NextLine(&line_info);
// If this fragment will be larger than the inline-size of the opportunity,
// *and* the opportunity is smaller than the available inline-size, and the
@@ -692,29 +714,35 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
// We now can check the block-size of the fragment, and it fits within the
// opportunity.
- LayoutUnit block_size = container_builder_.ComputeBlockSize();
+ LayoutUnit line_height = container_builder_.LineHeight();
// Now that we have the block-size of the line, we can re-test the layout
// opportunity to see if we fit into the (potentially) non-rectangular
// shape area.
+ //
// If the AvailableInlineSize changes we need to run the line breaker again
// with the calculated line_block_size. This is *safe* as the line breaker
// won't produce a line which has a larger block-size, (as it can only
// decrease or stay the same size).
- if (UNLIKELY(opportunity.HasShapeExclusions())) {
+ //
+ // We skip attempting to fit empty lines into the shape area, as they
+ // should only contain floats and/or abs-pos which shouldn't be affected by
+ // this logic.
+ if (UNLIKELY(opportunity.HasShapeExclusions() &&
+ !line_info.IsEmptyLine())) {
NGLineLayoutOpportunity line_opportunity_with_height =
opportunity.ComputeLineLayoutOpportunity(ConstraintSpace(),
- block_size, block_delta);
+ line_height, block_delta);
if (line_opportunity_with_height.AvailableInlineSize() !=
line_opportunity.AvailableInlineSize()) {
- line_block_size = block_size;
+ line_block_size = line_height;
continue;
}
}
// Check if the line will fit in the current opportunity.
- if (block_size + block_delta > opportunity.rect.BlockSize()) {
+ if (line_height + block_delta > opportunity.rect.BlockSize()) {
block_delta = LayoutUnit();
line_block_size = LayoutUnit();
++opportunities_it;
@@ -725,74 +753,64 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
ConstraintSpace().BfcOffset().block_offset)
container_builder_.SetIsPushedByFloats();
- LayoutUnit line_height = container_builder_.LineHeight();
-
// Success!
positioned_floats_.AppendVector(positioned_floats);
container_builder_.SetBreakToken(
line_breaker.CreateBreakToken(line_info, std::move(box_states_)));
- // Place any remaining floats which couldn't fit on the line.
- PositionPendingFloats(line_height, exclusion_space.get());
-
- // A <br clear=both> will strech the line-box height, such that the
- // block-end edge will clear any floats.
- // TODO(ikilpatrick): Move this into ng_block_layout_algorithm.
- container_builder_.SetBlockSize(
- ComputeContentSize(line_info, *exclusion_space, line_height));
-
+ if (is_empty_inline) {
+ DCHECK_EQ(container_builder_.BlockSize(), 0);
+ } else {
+ // Place any remaining floats which couldn't fit on the line.
+ PositionPendingFloats(line_height, exclusion_space.get());
+
+ // A <br clear=both> will strech the line-box height, such that the
+ // block-end edge will clear any floats.
+ // TODO(ikilpatrick): Move this into ng_block_layout_algorithm.
+ container_builder_.SetBlockSize(
+ ComputeContentSize(line_info, *exclusion_space, line_height));
+ }
break;
}
// We shouldn't have any unpositioned floats if we aren't empty.
- DCHECK(unpositioned_floats_.IsEmpty());
+ DCHECK(unpositioned_floats_.IsEmpty() || is_empty_inline);
container_builder_.SwapPositionedFloats(&positioned_floats_);
container_builder_.SetExclusionSpace(
exclusion_space ? std::move(exclusion_space)
: std::move(initial_exclusion_space));
-
- Vector<NGOutOfFlowPositionedDescendant> descendant_candidates;
- container_builder_.GetAndClearOutOfFlowDescendantCandidates(
- &descendant_candidates, nullptr);
- for (auto& descendant : descendant_candidates)
- container_builder_.AddOutOfFlowDescendant(descendant);
+ container_builder_.MoveOutOfFlowDescendantCandidatesToDescendants();
return container_builder_.ToLineBoxFragment();
}
// This positions any "leading" floats within the given exclusion space.
// If we are also an empty inline, it will add any out-of-flow descendants.
-// TODO(ikilpatrick): Do we need to always add the OOFs here?
unsigned NGInlineLayoutAlgorithm::PositionLeadingItems(
NGExclusionSpace* exclusion_space) {
const Vector<NGInlineItem>& items = Node().ItemsData(false).items;
- bool is_empty_inline = Node().IsEmptyInline();
LayoutUnit bfc_line_offset = ConstraintSpace().BfcOffset().line_offset;
unsigned index = BreakToken() ? BreakToken()->ItemIndex() : 0;
for (; index < items.size(); ++index) {
- const auto& item = items[index];
+ const NGInlineItem& item = items[index];
if (item.Type() == NGInlineItem::kFloating) {
NGBlockNode node(ToLayoutBox(item.GetLayoutObject()));
NGBoxStrut margins =
ComputeMarginsForContainer(ConstraintSpace(), node.Style());
- auto unpositioned_float = NGUnpositionedFloat::Create(
- ConstraintSpace().AvailableSize(),
- ConstraintSpace().PercentageResolutionSize(), bfc_line_offset,
- bfc_line_offset, margins, node, /* break_token */ nullptr);
+ scoped_refptr<NGUnpositionedFloat> unpositioned_float =
+ NGUnpositionedFloat::Create(
+ ConstraintSpace().AvailableSize(),
+ ConstraintSpace().PercentageResolutionSize(), bfc_line_offset,
+ bfc_line_offset, margins, node, /* break_token */ nullptr);
AddUnpositionedFloat(&unpositioned_floats_, &container_builder_,
std::move(unpositioned_float));
- } else if (is_empty_inline &&
- item.Type() == NGInlineItem::kOutOfFlowPositioned) {
- NGBlockNode node(ToLayoutBox(item.GetLayoutObject()));
- container_builder_.AddInlineOutOfFlowChildCandidate(
- node, NGLogicalOffset(), Style().Direction(), nullptr);
}
// Abort if we've found something that makes this a non-empty inline.
if (!item.IsEmptyItem()) {
- DCHECK(!is_empty_inline);
+ DCHECK(!Node().IsEmptyInline());
break;
}
}
@@ -821,7 +839,7 @@ void NGInlineLayoutAlgorithm::PositionPendingFloats(
LayoutUnit origin_block_offset = bfc_offset.block_offset + content_size;
LayoutUnit from_block_offset = bfc_offset.block_offset;
- const auto positioned_floats =
+ const Vector<NGPositionedFloat> positioned_floats =
PositionFloats(origin_block_offset, from_block_offset,
unpositioned_floats_, ConstraintSpace(), exclusion_space);
@@ -843,8 +861,8 @@ void NGInlineLayoutAlgorithm::BidiReorder() {
Vector<UBiDiLevel, 32> levels;
logical_items.ReserveInitialCapacity(line_box_.size());
levels.ReserveInitialCapacity(line_box_.size());
- for (auto& item : line_box_) {
- if (!item.HasFragment() && !item.HasBidiLevel())
+ for (NGLineBoxFragmentBuilder::Child& item : line_box_) {
+ if (item.IsPlaceholder())
continue;
levels.push_back(item.bidi_level);
logical_items.AddChild(std::move(item));
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h
index 017544f453a..4fcf7b1026a 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h
@@ -72,7 +72,7 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final
void PlaceLayoutResult(NGInlineItemResult*,
NGInlineBoxState*,
LayoutUnit inline_offset = LayoutUnit());
- bool PlaceOutOfFlowObjects(const NGLineInfo&, const NGLineHeightMetrics&);
+ void PlaceOutOfFlowObjects(const NGLineInfo&, const NGLineHeightMetrics&);
void PlaceListMarker(const NGInlineItem&,
NGInlineItemResult*,
const NGLineInfo&);
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
index 654b7eb887e..6476e38750a 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
@@ -266,10 +266,11 @@ TEST_F(NGInlineLayoutAlgorithmTest, TextFloatsAroundFloatsBefore) {
ToNGPhysicalBoxFragment(html_fragment->Children()[0].get());
auto* container_fragment =
ToNGPhysicalBoxFragment(body_fragment->Children()[0].get());
- auto* span_box_fragments_wrapper =
- ToNGPhysicalBoxFragment(container_fragment->Children()[3].get());
Vector<NGPhysicalLineBoxFragment*> line_boxes;
- for (const auto& child : span_box_fragments_wrapper->Children()) {
+ for (const auto& child : container_fragment->Children()) {
+ if (!child->IsLineBox())
+ continue;
+
line_boxes.push_back(ToNGPhysicalLineBoxFragment(child.get()));
}
@@ -433,8 +434,8 @@ TEST_F(NGInlineLayoutAlgorithmTest, PositionFloatsWithMargins) {
EXPECT_EQ(LayoutUnit(53), span->OffsetLeft());
}
-// Test glyph bounding box causes visual overflow.
-TEST_F(NGInlineLayoutAlgorithmTest, VisualRect) {
+// Test glyph bounding box causes ink overflow.
+TEST_F(NGInlineLayoutAlgorithmTest, InkOverflow) {
LoadAhem();
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
@@ -452,9 +453,9 @@ TEST_F(NGInlineLayoutAlgorithmTest, VisualRect) {
EXPECT_EQ(LayoutUnit(10), box_fragment->Size().height);
- NGPhysicalOffsetRect visual_rect = box_fragment->ContentsVisualRect();
- EXPECT_EQ(LayoutUnit(-5), visual_rect.offset.top);
- EXPECT_EQ(LayoutUnit(20), visual_rect.size.height);
+ NGPhysicalOffsetRect ink_overflow = box_fragment->ContentsInkOverflow();
+ EXPECT_EQ(LayoutUnit(-5), ink_overflow.offset.top);
+ EXPECT_EQ(LayoutUnit(20), ink_overflow.size.height);
}
TEST_F(NGInlineLayoutAlgorithmTest,
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
index bf394eba54f..9cecafec27e 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
@@ -8,6 +8,7 @@
#include <memory>
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
+#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h"
@@ -19,13 +20,16 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
#include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h"
+#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h"
+#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
#include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
-#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/run_segmenter.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h"
#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
@@ -65,6 +69,9 @@ void CollectInlinesInternal(
String* previous_text) {
builder->EnterBlock(block->Style());
LayoutObject* node = GetLayoutObjectForFirstChildNode(block);
+
+ const LayoutObject* symbol =
+ LayoutNGListItem::FindSymbolMarkerLayoutText(block);
while (node) {
if (node->IsText()) {
LayoutText* layout_text = ToLayoutText(node);
@@ -87,6 +94,10 @@ void CollectInlinesInternal(
else
builder->Append(layout_text->GetText(), node->Style(), layout_text);
}
+
+ if (symbol == layout_text)
+ builder->SetIsSymbolMarker(true);
+
ClearNeedsLayoutIfUpdatingLayout<OffsetMappingBuilder>(layout_text);
} else if (node->IsFloating()) {
@@ -195,19 +206,8 @@ bool NGInlineNode::InLineHeightQuirksMode() const {
}
bool NGInlineNode::CanContainFirstFormattedLine() const {
- // TODO(kojii): In LayoutNG, leading OOF creates an anonymous block box,
- // and that |LayoutBlockFlow::CanContainFirstFormattedLine()| does not work.
- // crbug.com/734554
- LayoutObject* layout_object = GetLayoutBlockFlow();
- if (!layout_object->IsAnonymousBlock())
- return true;
- for (;;) {
- layout_object = layout_object->PreviousSibling();
- if (!layout_object)
- return true;
- if (!layout_object->IsFloatingOrOutOfFlowPositioned())
- return false;
- }
+ DCHECK(GetLayoutBlockFlow());
+ return GetLayoutBlockFlow()->CanContainFirstFormattedLine();
}
NGInlineNodeData* NGInlineNode::MutableData() {
@@ -326,6 +326,71 @@ void NGInlineNode::CollectInlines(NGInlineNodeData* data,
}
void NGInlineNode::SegmentText(NGInlineNodeData* data) {
+ SegmentBidiRuns(data);
+ SegmentScriptRuns(data);
+ SegmentFontOrientation(data);
+}
+
+// Segment NGInlineItem by script, Emoji, and orientation using RunSegmenter.
+void NGInlineNode::SegmentScriptRuns(NGInlineNodeData* data) {
+ if (data->text_content.Is8Bit() && !data->is_bidi_enabled_) {
+ if (data->items.size()) {
+ RunSegmenter::RunSegmenterRange range = {
+ 0u, data->text_content.length(), USCRIPT_LATIN,
+ OrientationIterator::kOrientationKeep, FontFallbackPriority::kText};
+ NGInlineItem::PopulateItemsFromRun(data->items, 0, range);
+ }
+ return;
+ }
+
+ // Segment by script and Emoji.
+ // Orientation is segmented separately, because it may vary by items.
+ Vector<NGInlineItem>& items = data->items;
+ String& text_content = data->text_content;
+ text_content.Ensure16Bit();
+ RunSegmenter segmenter(text_content.Characters16(), text_content.length(),
+ FontOrientation::kHorizontal);
+ RunSegmenter::RunSegmenterRange range = RunSegmenter::NullRange();
+ for (unsigned item_index = 0; segmenter.Consume(&range);) {
+ DCHECK_EQ(items[item_index].start_offset_, range.start);
+ item_index = NGInlineItem::PopulateItemsFromRun(items, item_index, range);
+ }
+}
+
+void NGInlineNode::SegmentFontOrientation(NGInlineNodeData* data) {
+ // Segment by orientation, only if vertical writing mode and items with
+ // 'text-orientation: mixed'.
+ if (GetLayoutBlockFlow()->IsHorizontalWritingMode())
+ return;
+
+ Vector<NGInlineItem>& items = data->items;
+ String& text_content = data->text_content;
+ text_content.Ensure16Bit();
+
+ for (unsigned item_index = 0; item_index < items.size();) {
+ NGInlineItem& item = items[item_index];
+ if (item.Type() != NGInlineItem::kText ||
+ item.Style()->GetFont().GetFontDescription().Orientation() !=
+ FontOrientation::kVerticalMixed) {
+ item_index++;
+ continue;
+ }
+ unsigned start_offset = item.StartOffset();
+ OrientationIterator iterator(text_content.Characters16() + start_offset,
+ item.Length(),
+ FontOrientation::kVerticalMixed);
+ unsigned end_offset;
+ OrientationIterator::RenderOrientation orientation;
+ while (iterator.Consume(&end_offset, &orientation)) {
+ item_index = NGInlineItem::PopulateItemsFromFontOrientation(
+ items, item_index, end_offset + start_offset, orientation);
+ }
+ }
+}
+
+// Segment bidi runs by resolving bidi embedding levels.
+// http://unicode.org/reports/tr9/#Resolving_Embedding_Levels
+void NGInlineNode::SegmentBidiRuns(NGInlineNodeData* data) {
if (!data->is_bidi_enabled_) {
data->SetBaseDirection(TextDirection::kLtr);
return;
@@ -370,8 +435,6 @@ void NGInlineNode::SegmentText(NGInlineNodeData* data) {
void NGInlineNode::ShapeText(NGInlineItemsData* data,
NGInlineItemsData* previous_data) {
- // TODO(eae): Add support for shaping latin-1 text?
- data->text_content.Ensure16Bit();
ShapeText(data->text_content, &data->items,
previous_data ? &previous_data->text_content : nullptr);
}
@@ -380,7 +443,7 @@ void NGInlineNode::ShapeText(const String& text_content,
Vector<NGInlineItem>* items,
const String* previous_text) {
// Provide full context of the entire node to the shaper.
- HarfBuzzShaper shaper(text_content.Characters16(), text_content.length());
+ HarfBuzzShaper shaper(text_content);
ShapeResultSpacing<String> spacing(text_content);
for (unsigned index = 0; index < items->size();) {
@@ -405,9 +468,12 @@ void NGInlineNode::ShapeText(const String& text_content,
if (item.Type() == NGInlineItem::kText) {
// Shape adjacent items together if the font and direction matches to
// allow ligatures and kerning to apply.
+ // Also run segment properties must match because NGInlineItem gives
+ // pre-segmented range to HarfBuzzShaper.
// TODO(kojii): Figure out the exact conditions under which this
// behavior is desirable.
- if (font != item.Style()->GetFont() || direction != item.Direction())
+ if (font != item.Style()->GetFont() || direction != item.Direction() ||
+ !item.EqualsRunSegment(start_item))
break;
end_offset = item.EndOffset();
} else if (item.Type() == NGInlineItem::kOpenTag ||
@@ -459,8 +525,11 @@ void NGInlineNode::ShapeText(const String& text_content,
}
// Shape each item with the full context of the entire node.
- scoped_refptr<ShapeResult> shape_result =
- shaper.Shape(&font, direction, start_item.StartOffset(), end_offset);
+ RunSegmenter::RunSegmenterRange range =
+ start_item.CreateRunSegmenterRange();
+ range.end = end_offset;
+ scoped_refptr<ShapeResult> shape_result = shaper.Shape(
+ &font, direction, start_item.StartOffset(), end_offset, &range);
if (UNLIKELY(spacing.SetSpacing(font.GetFontDescription())))
shape_result->ApplySpacing(spacing);
@@ -494,7 +563,7 @@ void NGInlineNode::ShapeText(const String& text_content,
void NGInlineNode::ShapeTextForFirstLineIfNeeded(NGInlineNodeData* data) {
// First check if the document has any :first-line rules.
DCHECK(!data->first_line_items_);
- LayoutObject* layout_object = GetLayoutObject();
+ LayoutObject* layout_object = GetLayoutBox();
if (!layout_object->GetDocument().GetStyleEngine().UsesFirstLineRules())
return;
@@ -533,6 +602,22 @@ void NGInlineNode::ShapeTextForFirstLineIfNeeded(NGInlineNodeData* data) {
}
}
+ // Check if we have a first-line anonymous inline box. It is the first
+ // open-tag if we have.
+ for (auto& item : first_line_items->items) {
+ if (item.Type() == NGInlineItem::kOpenTag) {
+ if (item.layout_object_->IsAnonymous() &&
+ item.layout_object_->IsLayoutInline() &&
+ item.layout_object_->Parent() == GetLayoutBox() &&
+ ToLayoutInline(item.layout_object_)->IsFirstLineAnonymous()) {
+ item.should_create_box_fragment_ = true;
+ }
+ break;
+ }
+ if (item.Type() != NGInlineItem::kBidiControl)
+ break;
+ }
+
// Re-shape if the font is different.
if (needs_reshape || FirstLineNeedsReshape(*first_line_style, *block_style))
ShapeText(first_line_items.get());
@@ -583,26 +668,28 @@ static LayoutUnit ComputeContentSize(NGInlineNode node,
Vector<scoped_refptr<NGUnpositionedFloat>> unpositioned_floats;
scoped_refptr<NGInlineBreakToken> break_token;
- NGLineInfo line_info;
NGExclusionSpace empty_exclusion_space;
NGLineLayoutOpportunity line_opportunity(available_inline_size);
LayoutUnit result;
LayoutUnit previous_floats_inline_size =
input.float_left_inline_size + input.float_right_inline_size;
+ DCHECK_GE(previous_floats_inline_size, 0);
while (!break_token || !break_token->IsFinished()) {
unpositioned_floats.clear();
- NGLineBreaker line_breaker(node, mode, *space, &positioned_floats,
- &unpositioned_floats,
- nullptr /* container_builder */,
- &empty_exclusion_space, 0u, break_token.get());
- if (!line_breaker.NextLine(line_opportunity, &line_info))
+ NGLineInfo line_info;
+ NGLineBreaker line_breaker(
+ node, mode, *space, &positioned_floats, &unpositioned_floats,
+ nullptr /* container_builder */, &empty_exclusion_space, 0u,
+ line_opportunity, break_token.get());
+ line_breaker.NextLine(&line_info);
+
+ if (line_info.Results().IsEmpty())
break;
break_token = line_breaker.CreateBreakToken(line_info, nullptr);
- LayoutUnit inline_size = line_info.TextIndent();
- for (const NGInlineItemResult item_result : line_info.Results())
- inline_size += item_result.inline_size;
+ LayoutUnit inline_size = line_info.Width();
+ DCHECK_EQ(inline_size, line_info.ComputeWidth().ClampNegativeToZero());
// There should be no positioned floats while determining the min/max sizes.
DCHECK_EQ(positioned_floats.size(), 0u);
@@ -643,7 +730,10 @@ static LayoutUnit ComputeContentSize(NGInlineNode node,
floats_inline_size = LayoutUnit();
}
- floats_inline_size += child_sizes.max_size + child_inline_margins;
+ // When negative margins move the float outside the content area,
+ // such float should not affect the content size.
+ floats_inline_size +=
+ (child_sizes.max_size + child_inline_margins).ClampNegativeToZero();
previous_float_type = float_style.Floating();
}
}
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
index 7685706584d..73d49d80621 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
@@ -80,6 +80,9 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
void CollectInlines(NGInlineNodeData*,
NGInlineNodeData* previous_data = nullptr);
void SegmentText(NGInlineNodeData*);
+ void SegmentScriptRuns(NGInlineNodeData*);
+ void SegmentFontOrientation(NGInlineNodeData*);
+ void SegmentBidiRuns(NGInlineNodeData*);
void ShapeText(NGInlineItemsData*,
NGInlineItemsData* previous_data = nullptr);
void ShapeText(const String& text,
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
index f04e5e70b56..c06d5dcefb0 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
@@ -359,7 +359,7 @@ TEST_F(NGInlineNodeTest, SegmentBidiIsolate) {
NGInlineNodeForTest node = CreateInlineNode();
node = CreateBidiIsolateNode(node, style_.get(), layout_object_);
Vector<NGInlineItem>& items = node.Items();
- ASSERT_EQ(9u, items.size());
+ ASSERT_EQ(10u, items.size());
TEST_ITEM_OFFSET_DIR(items[0], 0u, 6u, TextDirection::kLtr);
TEST_ITEM_OFFSET_DIR(items[1], 6u, 7u, TextDirection::kLtr);
TEST_ITEM_OFFSET_DIR(items[2], 7u, 13u, TextDirection::kRtl);
@@ -368,7 +368,8 @@ TEST_F(NGInlineNodeTest, SegmentBidiIsolate) {
TEST_ITEM_OFFSET_DIR(items[5], 15u, 16u, TextDirection::kRtl);
TEST_ITEM_OFFSET_DIR(items[6], 16u, 21u, TextDirection::kRtl);
TEST_ITEM_OFFSET_DIR(items[7], 21u, 22u, TextDirection::kLtr);
- TEST_ITEM_OFFSET_DIR(items[8], 22u, 28u, TextDirection::kLtr);
+ TEST_ITEM_OFFSET_DIR(items[8], 22u, 23u, TextDirection::kLtr);
+ TEST_ITEM_OFFSET_DIR(items[9], 23u, 28u, TextDirection::kLtr);
}
#define TEST_TEXT_FRAGMENT(fragment, start_offset, end_offset) \
@@ -385,12 +386,13 @@ TEST_F(NGInlineNodeTest, CreateLineBidiIsolate) {
node.ShapeText();
Vector<scoped_refptr<const NGPhysicalTextFragment>> fragments;
CreateLine(node, &fragments);
- ASSERT_EQ(5u, fragments.size());
+ ASSERT_EQ(6u, fragments.size());
TEST_TEXT_FRAGMENT(fragments[0], 0u, 6u);
TEST_TEXT_FRAGMENT(fragments[1], 16u, 21u);
TEST_TEXT_FRAGMENT(fragments[2], 14u, 15u);
TEST_TEXT_FRAGMENT(fragments[3], 7u, 13u);
- TEST_TEXT_FRAGMENT(fragments[4], 22u, 28u);
+ TEST_TEXT_FRAGMENT(fragments[4], 22u, 23u);
+ TEST_TEXT_FRAGMENT(fragments[5], 23u, 28u);
}
TEST_F(NGInlineNodeTest, MinMaxSize) {
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
index 2b7a7e3d58e..5e01951043f 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
@@ -36,19 +36,6 @@ LayoutUnit NGLineBoxFragmentBuilder::LineHeight() const {
return metrics_.LineHeight().ClampNegativeToZero();
}
-LayoutUnit NGLineBoxFragmentBuilder::ComputeBlockSize() const {
- LayoutUnit block_size;
- WritingMode writing_mode(node_.Style().GetWritingMode());
-
- for (size_t i = 0; i < children_.size(); ++i) {
- block_size = std::max(
- block_size, offsets_[i].block_offset +
- NGFragment(writing_mode, *children_[i]).BlockSize());
- }
-
- return block_size;
-}
-
const NGPhysicalFragment* NGLineBoxFragmentBuilder::Child::PhysicalFragment()
const {
return layout_result ? layout_result->PhysicalFragment().get()
@@ -142,25 +129,21 @@ void NGLineBoxFragmentBuilder::AddChildren(ChildList& children) {
scoped_refptr<NGLayoutResult> NGLineBoxFragmentBuilder::ToLineBoxFragment() {
DCHECK_EQ(offsets_.size(), children_.size());
- WritingMode writing_mode(node_.Style().GetWritingMode());
- NGPhysicalSize physical_size = Size().ConvertToPhysical(writing_mode);
+ WritingMode line_writing_mode(ToLineWritingMode(GetWritingMode()));
+ NGPhysicalSize physical_size = Size().ConvertToPhysical(line_writing_mode);
- NGPhysicalOffsetRect contents_visual_rect({}, physical_size);
- NGPhysicalOffsetRect scrollable_overflow({}, physical_size);
+ NGPhysicalOffsetRect contents_ink_overflow({}, physical_size);
for (size_t i = 0; i < children_.size(); ++i) {
NGPhysicalFragment* child = children_[i].get();
child->SetOffset(offsets_[i].ConvertToPhysical(
- writing_mode, Direction(), physical_size, child->Size()));
- child->PropagateContentsVisualRect(&contents_visual_rect);
- NGPhysicalOffsetRect child_scroll_overflow = child->ScrollableOverflow();
- child_scroll_overflow.offset += child->Offset();
- scrollable_overflow.Unite(child_scroll_overflow);
+ line_writing_mode, Direction(), physical_size, child->Size()));
+ child->PropagateContentsInkOverflow(&contents_ink_overflow);
}
scoped_refptr<NGPhysicalLineBoxFragment> fragment =
base::AdoptRef(new NGPhysicalLineBoxFragment(
Style(), style_variant_, physical_size, children_,
- contents_visual_rect, scrollable_overflow, metrics_, base_direction_,
+ contents_ink_overflow, metrics_, base_direction_,
break_token_ ? std::move(break_token_)
: NGInlineBreakToken::Create(node_)));
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h
index 9ea29183fb1..be0369eaaf2 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h
@@ -22,6 +22,7 @@ struct NGPositionedFloat;
class CORE_EXPORT NGLineBoxFragmentBuilder final
: public NGContainerFragmentBuilder {
STACK_ALLOCATED();
+
public:
NGLineBoxFragmentBuilder(NGInlineNode,
scoped_refptr<const ComputedStyle>,
@@ -32,7 +33,6 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
void Reset();
LayoutUnit LineHeight() const;
- LayoutUnit ComputeBlockSize() const;
const NGLineHeightMetrics& Metrics() const { return metrics_; }
void SetMetrics(const NGLineHeightMetrics&);
@@ -107,6 +107,7 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
return HasInFlowFragment() || HasOutOfFlowFragment();
}
bool HasBidiLevel() const { return bidi_level != 0xff; }
+ bool IsPlaceholder() const { return !HasFragment() && !HasBidiLevel(); }
const NGPhysicalFragment* PhysicalFragment() const;
};
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
index 79294415634..31fa4092c6d 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
@@ -4,6 +4,7 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h"
+#include "third_party/blink/renderer/core/layout/layout_list_marker.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
@@ -33,19 +34,12 @@ inline bool CanBreakAfterLast(const NGInlineItemResults& item_results) {
return !item_results.IsEmpty() && item_results.back().can_break_after;
}
-} // namespace
-
-NGLineBreaker::LineData::LineData(NGInlineNode node,
- const NGInlineBreakToken* break_token) {
- is_first_formatted_line = (!break_token || (!break_token->ItemIndex() &&
- !break_token->TextOffset())) &&
- node.CanContainFirstFormattedLine();
- use_first_line_style = is_first_formatted_line && node.GetLayoutObject()
- ->GetDocument()
- .GetStyleEngine()
- .UsesFirstLineRules();
+inline bool ShouldCreateLineBox(const NGInlineItemResults& item_results) {
+ return !item_results.IsEmpty() && item_results.back().should_create_line_box;
}
+} // namespace
+
NGLineBreaker::NGLineBreaker(
NGInlineNode node,
NGLineBreakerMode mode,
@@ -55,10 +49,20 @@ NGLineBreaker::NGLineBreaker(
NGContainerFragmentBuilder* container_builder,
NGExclusionSpace* exclusion_space,
unsigned handled_float_index,
+ const NGLineLayoutOpportunity& line_opportunity,
const NGInlineBreakToken* break_token)
- : line_(node, break_token),
+ : line_opportunity_(line_opportunity),
node_(node),
- items_data_(node.ItemsData(line_.use_first_line_style)),
+ is_first_formatted_line_((!break_token || (!break_token->ItemIndex() &&
+ !break_token->TextOffset())) &&
+ node.CanContainFirstFormattedLine()),
+ use_first_line_style_(is_first_formatted_line_ &&
+ node.GetLayoutBox()
+ ->GetDocument()
+ .GetStyleEngine()
+ .UsesFirstLineRules()),
+ in_line_height_quirks_mode_(node.InLineHeightQuirksMode()),
+ items_data_(node.ItemsData(use_first_line_style_)),
mode_(mode),
constraint_space_(space),
positioned_floats_(positioned_floats),
@@ -66,12 +70,10 @@ NGLineBreaker::NGLineBreaker(
container_builder_(container_builder),
exclusion_space_(exclusion_space),
break_iterator_(items_data_.text_content),
- shaper_(items_data_.text_content.Characters16(),
- items_data_.text_content.length()),
+ shaper_(items_data_.text_content),
spacing_(items_data_.text_content),
handled_floats_end_item_index_(handled_float_index),
- base_direction_(node_.BaseDirection()),
- in_line_height_quirks_mode_(node.InLineHeightQuirksMode()) {
+ base_direction_(node_.BaseDirection()) {
break_iterator_.SetBreakSpace(BreakSpaceType::kBeforeSpaceRun);
if (break_token) {
@@ -88,36 +90,32 @@ NGLineBreaker::NGLineBreaker(
// header.
NGLineBreaker::~NGLineBreaker() = default;
-inline NGInlineItemResult* NGLineBreaker::AddItem(
- const NGInlineItem& item,
- unsigned end_offset,
- NGInlineItemResults* item_results) {
+inline NGInlineItemResult* NGLineBreaker::AddItem(const NGInlineItem& item,
+ unsigned end_offset) {
DCHECK_LE(end_offset, item.EndOffset());
- item_results->push_back(
- NGInlineItemResult(&item, item_index_, offset_, end_offset));
- return &item_results->back();
+ item_results_->push_back(
+ NGInlineItemResult(&item, item_index_, offset_, end_offset,
+ ShouldCreateLineBox(*item_results_)));
+ return &item_results_->back();
}
-inline NGInlineItemResult* NGLineBreaker::AddItem(
- const NGInlineItem& item,
- NGInlineItemResults* item_results) {
- return AddItem(item, item.EndOffset(), item_results);
+inline NGInlineItemResult* NGLineBreaker::AddItem(const NGInlineItem& item) {
+ return AddItem(item, item.EndOffset());
}
void NGLineBreaker::SetLineEndFragment(
- scoped_refptr<NGPhysicalTextFragment> fragment,
- NGLineInfo* line_info) {
+ scoped_refptr<NGPhysicalTextFragment> fragment) {
bool is_horizontal =
IsHorizontalWritingMode(constraint_space_.GetWritingMode());
- if (line_info->LineEndFragment()) {
- const NGPhysicalSize& size = line_info->LineEndFragment()->Size();
- line_.position -= is_horizontal ? size.width : size.height;
+ if (line_info_->LineEndFragment()) {
+ const NGPhysicalSize& size = line_info_->LineEndFragment()->Size();
+ position_ -= is_horizontal ? size.width : size.height;
}
if (fragment) {
const NGPhysicalSize& size = fragment->Size();
- line_.position += is_horizontal ? size.width : size.height;
+ position_ += is_horizontal ? size.width : size.height;
}
- line_info->SetLineEndFragment(std::move(fragment));
+ line_info_->SetLineEndFragment(std::move(fragment));
}
inline void NGLineBreaker::ComputeCanBreakAfter(
@@ -126,27 +124,15 @@ inline void NGLineBreaker::ComputeCanBreakAfter(
auto_wrap_ && break_iterator_.IsBreakable(item_result->end_offset);
}
-// True if |item| is trailing; i.e., |item| and all items after it are opaque to
-// whitespace collapsing.
-bool NGLineBreaker::IsTrailing(const NGInlineItem& item,
- const NGLineInfo& line_info) const {
- const Vector<NGInlineItem>& items = line_info.ItemsData().items;
- for (const NGInlineItem* it = &item; it != items.end(); ++it) {
- if (it->EndCollapseType() != NGInlineItem::kOpaqueToCollapsing)
- return false;
- }
- return true;
-}
-
// Compute the base direction for bidi algorithm for this line.
-void NGLineBreaker::ComputeBaseDirection(const NGLineInfo& line_info) {
+void NGLineBreaker::ComputeBaseDirection() {
// If 'unicode-bidi' is not 'plaintext', use the base direction of the block.
if (!previous_line_had_forced_break_ ||
node_.Style().GetUnicodeBidi() != UnicodeBidi::kPlaintext)
return;
// If 'unicode-bidi: plaintext', compute the base direction for each paragraph
// (separated by forced break.)
- const String& text = line_info.ItemsData().text_content;
+ const String& text = Text();
if (text.Is8Bit())
return;
size_t end_offset = text.find(kNewlineCharacter, offset_);
@@ -157,67 +143,77 @@ void NGLineBreaker::ComputeBaseDirection(const NGLineInfo& line_info) {
}
// Initialize internal states for the next line.
-void NGLineBreaker::PrepareNextLine(
- const NGLineLayoutOpportunity& line_opportunity,
- NGLineInfo* line_info) {
- NGInlineItemResults* item_results = &line_info->Results();
- item_results->clear();
- line_info->SetStartOffset(offset_);
- line_info->SetLineStyle(
- node_, items_data_, constraint_space_, line_.is_first_formatted_line,
- line_.use_first_line_style, previous_line_had_forced_break_);
+void NGLineBreaker::PrepareNextLine() {
+ // NGLineInfo is not supposed to be re-used becase it's not much gain and to
+ // avoid rare code path.
+ DCHECK(item_results_->IsEmpty());
+
+ line_info_->SetStartOffset(offset_);
+ line_info_->SetLineStyle(node_, items_data_, constraint_space_,
+ is_first_formatted_line_, use_first_line_style_,
+ previous_line_had_forced_break_);
// Set the initial style of this line from the break token. Example:
// <p>...<span>....</span></p>
// When the line wraps in <span>, the 2nd line needs to start with the style
// of the <span>.
- override_break_anywhere_ = false;
- SetCurrentStyle(current_style_ ? *current_style_ : line_info->LineStyle());
- ComputeBaseDirection(*line_info);
- line_info->SetBaseDirection(base_direction_);
-
- line_.is_after_forced_break = false;
- line_.should_create_line_box = false;
+ SetCurrentStyle(current_style_ ? *current_style_ : line_info_->LineStyle());
+ ComputeBaseDirection();
+ line_info_->SetBaseDirection(base_direction_);
// Use 'text-indent' as the initial position. This lets tab positions to align
// regardless of 'text-indent'.
- line_.position = line_info->TextIndent();
-
- line_.line_opportunity = line_opportunity;
+ position_ = line_info_->TextIndent();
}
-bool NGLineBreaker::NextLine(const NGLineLayoutOpportunity& line_opportunity,
- NGLineInfo* line_info) {
- PrepareNextLine(line_opportunity, line_info);
- BreakLine(line_info);
+void NGLineBreaker::NextLine(NGLineInfo* line_info) {
+ line_info_ = line_info;
+ item_results_ = line_info->MutableResults();
+
+ PrepareNextLine();
+ BreakLine();
+ if (!trailing_spaces_collapsed_)
+ RemoveTrailingCollapsibleSpace();
- if (line_info->Results().IsEmpty())
- return false;
+#if DCHECK_IS_ON()
+ for (const auto& result : *item_results_)
+ result.CheckConsistency();
+#endif
+ // We should create a line-box when:
+ // - We have an item which needs a line box (text, etc).
+ // - A list-marker is present, and it would be the last line or last line
+ // before a forced new-line.
+ // - During min/max content sizing (to correctly determine the line width).
+ //
// TODO(kojii): There are cases where we need to PlaceItems() without creating
// line boxes. These cases need to be reviewed.
- if (line_.should_create_line_box)
- ComputeLineLocation(line_info);
-
- return true;
+ if (ShouldCreateLineBox(*item_results_) ||
+ (has_list_marker_ && line_info_->IsLastLine()) ||
+ mode_ != NGLineBreakerMode::kContent)
+ ComputeLineLocation();
+ else
+ line_info_->SetIsEmptyLine();
+
+ line_info_ = nullptr;
+ item_results_ = nullptr;
}
-void NGLineBreaker::BreakLine(NGLineInfo* line_info) {
- NGInlineItemResults* item_results = &line_info->Results();
- const Vector<NGInlineItem>& items = line_info->ItemsData().items;
- LineBreakState state = LineBreakState::kContinue;
- while (state != LineBreakState::kDone) {
+void NGLineBreaker::BreakLine() {
+ const Vector<NGInlineItem>& items = Items();
+ state_ = LineBreakState::kLeading;
+ while (state_ != LineBreakState::kDone) {
// Check overflow even if |item_index_| is at the end of the block, because
// the last item of the block may have caused overflow. In that case,
// |HandleOverflow| will rewind |item_index_|.
- if (state == LineBreakState::kContinue && auto_wrap_ && !line_.CanFit()) {
- state = HandleOverflow(line_info);
+ if (state_ == LineBreakState::kContinue && auto_wrap_ &&
+ position_ > AvailableWidthToFit()) {
+ HandleOverflow();
}
// If we reach at the end of the block, this is the last line.
DCHECK_LE(item_index_, items.size());
if (item_index_ == items.size()) {
- RemoveTrailingCollapsibleSpace(line_info);
- line_info->SetIsLastLine(true);
+ line_info_->SetIsLastLine(true);
return;
}
@@ -225,55 +221,57 @@ void NGLineBreaker::BreakLine(NGLineInfo* line_info) {
// They (or part of them) may also overhang the available width.
const NGInlineItem& item = items[item_index_];
if (item.Type() == NGInlineItem::kText) {
- state = HandleText(item, state, line_info);
+ HandleText(item);
#if DCHECK_IS_ON()
- if (!item_results->IsEmpty())
- item_results->back().CheckConsistency();
+ if (!item_results_->IsEmpty())
+ item_results_->back().CheckConsistency(true);
#endif
continue;
}
if (item.Type() == NGInlineItem::kCloseTag) {
- HandleCloseTag(item, item_results);
+ HandleCloseTag(item);
continue;
}
if (item.Type() == NGInlineItem::kControl) {
- state = HandleControlItem(item, state, line_info);
+ HandleControlItem(item);
+ continue;
+ }
+ if (item.Type() == NGInlineItem::kFloating) {
+ HandleFloat(item);
continue;
}
if (item.Type() == NGInlineItem::kBidiControl) {
- state = HandleBidiControlItem(item, state, line_info);
+ HandleBidiControlItem(item);
continue;
}
// Items after this point are not trailable. Break at the earliest break
// opportunity if we're trailing.
- if (state == LineBreakState::kTrailing &&
- CanBreakAfterLast(*item_results)) {
- line_info->SetIsLastLine(false);
+ if (state_ == LineBreakState::kTrailing &&
+ CanBreakAfterLast(*item_results_)) {
+ line_info_->SetIsLastLine(false);
return;
}
if (item.Type() == NGInlineItem::kAtomicInline) {
- HandleAtomicInline(item, line_info);
+ HandleAtomicInline(item);
} else if (item.Type() == NGInlineItem::kOpenTag) {
- HandleOpenTag(item, AddItem(item, item_results));
- } else if (item.Type() == NGInlineItem::kFloating) {
- HandleFloat(item, line_info, AddItem(item, item_results));
+ HandleOpenTag(item);
} else if (item.Type() == NGInlineItem::kOutOfFlowPositioned) {
DCHECK_EQ(item.Length(), 0u);
- AddItem(item, item_results);
+ AddItem(item);
MoveToNextOf(item);
} else if (item.Length()) {
NOTREACHED();
// For other items with text (e.g., bidi controls), use their text to
// determine the break opportunity.
- NGInlineItemResult* item_result = AddItem(item, item_results);
+ NGInlineItemResult* item_result = AddItem(item);
item_result->can_break_after =
break_iterator_.IsBreakable(item_result->end_offset);
MoveToNextOf(item);
} else if (item.Type() == NGInlineItem::kListMarker) {
- line_.should_create_line_box = true;
- NGInlineItemResult* item_result = AddItem(item, item_results);
+ NGInlineItemResult* item_result = AddItem(item);
+ has_list_marker_ = true;
DCHECK(!item_result->can_break_after);
MoveToNextOf(item);
} else {
@@ -283,84 +281,111 @@ void NGLineBreaker::BreakLine(NGLineInfo* line_info) {
}
}
-// Re-compute the current position from NGInlineItemResults.
+// Re-compute the current position from NGLineInfo.
// The current position is usually updated as NGLineBreaker builds
// NGInlineItemResults. This function re-computes it when it was lost.
-void NGLineBreaker::UpdatePosition(const NGInlineItemResults& results) {
- LayoutUnit position;
- for (const NGInlineItemResult& item_result : results)
- position += item_result.inline_size;
- line_.position = position;
+void NGLineBreaker::UpdatePosition() {
+ position_ = line_info_->ComputeWidth();
}
-void NGLineBreaker::ComputeLineLocation(NGLineInfo* line_info) const {
- LayoutUnit bfc_line_offset = line_.line_opportunity.line_left_offset;
- LayoutUnit available_width = line_.AvailableWidth();
+void NGLineBreaker::ComputeLineLocation() const {
+ LayoutUnit bfc_line_offset = line_opportunity_.line_left_offset;
+ LayoutUnit available_width = AvailableWidth();
+ DCHECK_EQ(position_, line_info_->ComputeWidth());
// Negative margins can make the position negative, but the inline size is
// always positive or 0.
- line_info->SetLineBfcOffset(
- {bfc_line_offset, line_.line_opportunity.bfc_block_offset},
- available_width, line_.position.ClampNegativeToZero());
+ line_info_->SetLineBfcOffset(
+ {bfc_line_offset, line_opportunity_.bfc_block_offset}, available_width,
+ position_.ClampNegativeToZero());
}
-NGLineBreaker::LineBreakState NGLineBreaker::HandleText(
- const NGInlineItem& item,
- LineBreakState state,
- NGLineInfo* line_info) {
+void NGLineBreaker::HandleText(const NGInlineItem& item) {
DCHECK_EQ(item.Type(), NGInlineItem::kText);
DCHECK(item.TextShapeResult());
- NGInlineItemResults* item_results = &line_info->Results();
// If we're trailing, only trailing spaces can be included in this line.
- if (state == LineBreakState::kTrailing && CanBreakAfterLast(*item_results)) {
- return HandleTrailingSpaces(item, line_info);
+ if (state_ == LineBreakState::kTrailing &&
+ CanBreakAfterLast(*item_results_)) {
+ return HandleTrailingSpaces(item);
}
- line_.should_create_line_box = true;
- NGInlineItemResult* item_result = AddItem(item, item_results);
- LayoutUnit available_width = line_.AvailableWidth();
+ // Skip leading collapsible spaces.
+ // Most cases such spaces are handled as trailing spaces of the previous line,
+ // but there are some cases doing so is too complex.
+ if (state_ == LineBreakState::kLeading) {
+ state_ = LineBreakState::kContinue;
+ if (item.Style()->CollapseWhiteSpace() &&
+ Text()[offset_] == kSpaceCharacter) {
+ // Skipping one whitespace removes all collapsible spaces because
+ // collapsible spaces are collapsed to single space in
+ // NGInlineItemBuilder.
+ ++offset_;
+ if (offset_ == item.EndOffset()) {
+ MoveToNextOf(item);
+ return;
+ }
+ }
+ }
+
+ NGInlineItemResult* item_result = AddItem(item);
+ item_result->should_create_line_box = true;
+ LayoutUnit available_width = AvailableWidthToFit();
if (auto_wrap_) {
// Try to break inside of this text item.
- BreakText(item_result, item, available_width - line_.position, line_info);
- LayoutUnit next_position = line_.position + item_result->inline_size;
+ BreakText(item_result, item, available_width - position_);
+
+ if (item.IsSymbolMarker()) {
+ LayoutUnit symbol_width = LayoutListMarker::WidthOfSymbol(*item.Style());
+ if (symbol_width > 0)
+ item_result->inline_size = symbol_width;
+ }
+
+ LayoutUnit next_position = position_ + item_result->inline_size;
bool is_overflow = next_position > available_width;
- line_.position = next_position;
+ DCHECK(is_overflow || item_result->shape_result);
+ position_ = next_position;
item_result->may_break_inside = !is_overflow;
MoveToNextOf(*item_result);
- if (!is_overflow || state == LineBreakState::kTrailing) {
+ if (!is_overflow ||
+ (state_ == LineBreakState::kTrailing && item_result->shape_result)) {
if (item_result->end_offset < item.EndOffset()) {
// The break point found, and text follows. Break here, after trailing
// spaces.
- return HandleTrailingSpaces(item, line_info);
+ return HandleTrailingSpaces(item);
}
// The break point found, but items that prohibit breaking before them may
// follow. Continue looking next items.
- return state;
+ return;
}
- return HandleOverflow(line_info);
+ return HandleOverflow();
}
// Add the rest of the item if !auto_wrap.
// Because the start position may need to reshape, run ShapingLineBreaker
// with max available width.
- BreakText(item_result, item, LayoutUnit::Max(), line_info);
+ BreakText(item_result, item, LayoutUnit::Max());
+
+ if (item.IsSymbolMarker()) {
+ LayoutUnit symbol_width = LayoutListMarker::WidthOfSymbol(*item.Style());
+ if (symbol_width > 0)
+ item_result->inline_size = symbol_width;
+ }
+
DCHECK_EQ(item_result->end_offset, item.EndOffset());
DCHECK(!item_result->may_break_inside);
item_result->can_break_after = false;
- line_.position += item_result->inline_size;
+ position_ += item_result->inline_size;
MoveToNextOf(item);
- return state;
}
void NGLineBreaker::BreakText(NGInlineItemResult* item_result,
const NGInlineItem& item,
- LayoutUnit available_width,
- NGLineInfo* line_info) {
+ LayoutUnit available_width) {
DCHECK_EQ(item.Type(), NGInlineItem::kText);
item.AssertOffset(item_result->start_offset);
@@ -370,19 +395,42 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result,
// expensive?
DCHECK_EQ(item.TextShapeResult()->StartIndexForResult(), item.StartOffset());
DCHECK_EQ(item.TextShapeResult()->EndIndexForResult(), item.EndOffset());
+ RunSegmenter::RunSegmenterRange segment_range =
+ item.CreateRunSegmenterRange();
ShapingLineBreaker breaker(&shaper_, &item.Style()->GetFont(),
item.TextShapeResult(), &break_iterator_,
- &spacing_, hyphenation_);
+ &segment_range, &spacing_, hyphenation_);
if (!enable_soft_hyphen_)
breaker.DisableSoftHyphen();
available_width = std::max(LayoutUnit(0), available_width);
+
+ // Use kStartShouldBeSafe if at the beginning of a line.
+ unsigned options = ShapingLineBreaker::kDefaultOptions;
+ if (offset_ != line_info_->StartOffset())
+ options |= ShapingLineBreaker::kDontReshapeStart;
+
+ // Use kNoResultIfOverflow if 'break-word' and we're trying to break normally
+ // because if this item overflows, we will rewind and break line again. The
+ // overflowing ShapeResult is not needed.
+ if (break_anywhere_if_overflow_ && !override_break_anywhere_)
+ options |= ShapingLineBreaker::kNoResultIfOverflow;
ShapingLineBreaker::Result result;
- scoped_refptr<ShapeResult> shape_result =
- breaker.ShapeLine(item_result->start_offset, available_width,
- offset_ == line_info->StartOffset(), &result);
- DCHECK_GT(shape_result->NumCharacters(), 0u);
+ scoped_refptr<const ShapeResult> shape_result = breaker.ShapeLine(
+ item_result->start_offset, available_width, options, &result);
+
+ // If this item overflows and 'break-word' is set, this line will be
+ // rewinded. Making this item long enough to overflow is enough.
+ if (!shape_result) {
+ DCHECK(options & ShapingLineBreaker::kNoResultIfOverflow);
+ item_result->inline_size = available_width + 1;
+ item_result->end_offset = item.EndOffset();
+ return;
+ }
+ DCHECK_EQ(shape_result->NumCharacters(),
+ result.break_offset - item_result->start_offset);
+
if (result.is_hyphenated) {
- AppendHyphen(item, line_info);
+ AppendHyphen(item);
// TODO(kojii): Implement when adding a hyphen caused overflow.
// crbug.com/714962: Should be removed when switched to NGPaint.
item_result->text_end_effect = NGTextEndEffect::kHyphen;
@@ -409,42 +457,103 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result,
}
}
-NGLineBreaker::LineBreakState NGLineBreaker::HandleTrailingSpaces(
- const NGInlineItem& item,
- NGLineInfo* line_info) {
+// Re-shape the specified range of |NGInlineItem|.
+scoped_refptr<ShapeResult> NGLineBreaker::ShapeText(const NGInlineItem& item,
+ unsigned start,
+ unsigned end) {
+ RunSegmenter::RunSegmenterRange segment_range =
+ item.CreateRunSegmenterRange();
+ scoped_refptr<ShapeResult> result = shaper_.Shape(
+ &item.Style()->GetFont(), item.TextShapeResult()->Direction(), start, end,
+ &segment_range);
+ if (UNLIKELY(spacing_.HasSpacing()))
+ result->ApplySpacing(spacing_);
+ return result;
+}
+
+// Compute a new ShapeResult for the specified end offset.
+// The end is re-shaped if it is not safe-to-break.
+scoped_refptr<ShapeResult> NGLineBreaker::TruncateLineEndResult(
+ const NGInlineItemResult& item_result,
+ unsigned end_offset) {
+ DCHECK(item_result.item);
+ DCHECK(item_result.shape_result);
+ const ShapeResult& source_result = *item_result.shape_result;
+ const unsigned start_offset = item_result.start_offset;
+ DCHECK_GE(start_offset, source_result.StartIndexForResult());
+ DCHECK_LE(end_offset, source_result.EndIndexForResult());
+ DCHECK(start_offset > source_result.StartIndexForResult() ||
+ end_offset < source_result.EndIndexForResult());
+
+ scoped_refptr<ShapeResult> new_result;
+ unsigned last_safe = source_result.PreviousSafeToBreakOffset(end_offset);
+ DCHECK_LE(last_safe, end_offset);
+ if (last_safe > start_offset)
+ new_result = source_result.SubRange(start_offset, last_safe);
+ if (last_safe < end_offset) {
+ scoped_refptr<ShapeResult> end_result = ShapeText(
+ *item_result.item, std::max(last_safe, start_offset), end_offset);
+ if (new_result)
+ end_result->CopyRange(0, end_offset, new_result.get());
+ else
+ new_result = std::move(end_result);
+ }
+ DCHECK(new_result);
+ return new_result;
+}
+
+// Update |ShapeResult| in |item_result| to match to its |start_offset| and
+// |end_offset|. The end is re-shaped if it is not safe-to-break.
+void NGLineBreaker::UpdateShapeResult(NGInlineItemResult* item_result) {
+ DCHECK(item_result);
+ item_result->shape_result =
+ TruncateLineEndResult(*item_result, item_result->end_offset);
+ DCHECK(item_result->shape_result);
+ item_result->inline_size = item_result->shape_result->SnappedWidth();
+}
+
+void NGLineBreaker::HandleTrailingSpaces(const NGInlineItem& item) {
DCHECK_EQ(item.Type(), NGInlineItem::kText);
DCHECK_LT(offset_, item.EndOffset());
const String& text = Text();
- NGInlineItemResults* item_results = &line_info->Results();
DCHECK(item.Style());
const ComputedStyle& style = *item.Style();
if (style.CollapseWhiteSpace()) {
- if (text[offset_] != kSpaceCharacter)
- return LineBreakState::kDone;
+ if (text[offset_] != kSpaceCharacter) {
+ state_ = LineBreakState::kDone;
+ return;
+ }
// Skipping one whitespace removes all collapsible spaces because
// collapsible spaces are collapsed to single space in NGInlineItemBuilder.
offset_++;
+ trailing_spaces_collapsed_ = true;
// Make the last item breakable after, even if it was nowrap.
- DCHECK(!item_results->IsEmpty());
- item_results->back().can_break_after = true;
+ DCHECK(!item_results_->IsEmpty());
+ item_results_->back().can_break_after = true;
} else {
// Find the end of the run of space characters in this item.
// Other white space characters (e.g., tab) are not included in this item.
DCHECK(style.BreakOnlyAfterWhiteSpace());
+ trailing_spaces_collapsed_ = true;
unsigned end = offset_;
while (end < item.EndOffset() && text[end] == kSpaceCharacter)
end++;
- if (end == offset_)
- return LineBreakState::kDone;
+ if (end == offset_) {
+ state_ = LineBreakState::kDone;
+ return;
+ }
- NGInlineItemResult* item_result = AddItem(item, end, item_results);
+ NGInlineItemResult* item_result = AddItem(item, end);
item_result->has_only_trailing_spaces = true;
- // TODO(kojii): Should reshape if it's not safe to break.
- item_result->shape_result = item.TextShapeResult()->SubRange(offset_, end);
- item_result->inline_size = item_result->shape_result->SnappedWidth();
- line_.position += item_result->inline_size;
+ item_result->shape_result = item.TextShapeResult();
+ if (item_result->start_offset == item.StartOffset() &&
+ item_result->end_offset == item.EndOffset())
+ item_result->inline_size = item_result->shape_result->SnappedWidth();
+ else
+ UpdateShapeResult(item_result);
+ position_ += item_result->inline_size;
item_result->can_break_after =
end < text.length() && !IsBreakableSpace(text[end]);
offset_ = end;
@@ -453,101 +562,143 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleTrailingSpaces(
// If non-space characters follow, the line is done.
// Otherwise keep checking next items for the break point.
DCHECK_LE(offset_, item.EndOffset());
- if (offset_ < item.EndOffset())
- return LineBreakState::kDone;
+ if (offset_ < item.EndOffset()) {
+ state_ = LineBreakState::kDone;
+ return;
+ }
item_index_++;
- return LineBreakState::kTrailing;
+ state_ = LineBreakState::kTrailing;
}
// Remove trailing collapsible spaces in |line_info|.
// https://drafts.csswg.org/css-text-3/#white-space-phase-2
-void NGLineBreaker::RemoveTrailingCollapsibleSpace(NGLineInfo* line_info) {
- NGInlineItemResults* item_results = &line_info->Results();
- if (item_results->IsEmpty())
+void NGLineBreaker::RemoveTrailingCollapsibleSpace() {
+ DCHECK(!trailing_spaces_collapsed_);
+
+ ComputeTrailingCollapsibleSpace();
+ trailing_spaces_collapsed_ = true;
+ if (!trailing_collapsible_space_.has_value())
return;
- for (auto it = item_results->rbegin(); it != item_results->rend(); ++it) {
+
+ // We have a trailing collapsible space. Remove it.
+ NGInlineItemResult* item_result = trailing_collapsible_space_->item_result;
+ position_ -= item_result->inline_size;
+ if (scoped_refptr<const ShapeResult>& collapsed_shape_result =
+ trailing_collapsible_space_->collapsed_shape_result) {
+ DCHECK_GE(item_result->end_offset, item_result->start_offset + 2);
+ --item_result->end_offset;
+ item_result->shape_result = std::move(collapsed_shape_result);
+ item_result->inline_size = item_result->shape_result->SnappedWidth();
+ position_ += item_result->inline_size;
+ } else {
+ item_results_->erase(item_result);
+ }
+ trailing_collapsible_space_.reset();
+}
+
+// Compute the width of trailing spaces without removing it.
+LayoutUnit NGLineBreaker::TrailingCollapsibleSpaceWidth() {
+ if (trailing_spaces_collapsed_)
+ return LayoutUnit();
+
+ ComputeTrailingCollapsibleSpace();
+ if (!trailing_collapsible_space_.has_value())
+ return LayoutUnit();
+
+ // Normally, the width of new_reuslt is smaller, but technically it can be
+ // larger. In such case, it means the trailing spaces has negative width.
+ NGInlineItemResult* item_result = trailing_collapsible_space_->item_result;
+ if (scoped_refptr<const ShapeResult>& collapsed_shape_result =
+ trailing_collapsible_space_->collapsed_shape_result) {
+ return item_result->inline_size - collapsed_shape_result->SnappedWidth();
+ }
+ return item_result->inline_size;
+}
+
+// Find trailing collapsible space if exists. The result is cached to
+// |trailing_collapsible_space_|.
+void NGLineBreaker::ComputeTrailingCollapsibleSpace() {
+ DCHECK(!trailing_spaces_collapsed_);
+
+ for (auto it = item_results_->rbegin(); it != item_results_->rend(); ++it) {
NGInlineItemResult& item_result = *it;
DCHECK(item_result.item);
const NGInlineItem& item = *item_result.item;
if (item.EndCollapseType() == NGInlineItem::kOpaqueToCollapsing)
continue;
if (item.Type() != NGInlineItem::kText)
- return;
- const String& text = Text();
- if (text[item_result.end_offset - 1] != kSpaceCharacter)
- return;
+ break;
+ DCHECK_GT(item_result.end_offset, 0u);
DCHECK(item.Style());
- if (!item.Style()->CollapseWhiteSpace())
- return;
+ if (Text()[item_result.end_offset - 1] != kSpaceCharacter ||
+ !item.Style()->CollapseWhiteSpace() ||
+ // |shape_result| is nullptr if this is an overflow because BreakText()
+ // uses kNoResultIfOverflow option.
+ !item_result.shape_result)
+ break;
- // We have a trailing collapsible space. Remove it.
- line_.position -= item_result.inline_size;
- --item_result.end_offset;
- if (item_result.end_offset == item_result.start_offset) {
- unsigned index = std::distance(item_results->begin(), &item_result);
- item_results->EraseAt(index);
- } else {
- // TODO(kojii): Should reshape if it's not safe to break.
- item_result.shape_result = item_result.shape_result->SubRange(
- item_result.start_offset, item_result.end_offset);
- item_result.inline_size = item_result.shape_result->SnappedWidth();
- line_.position += item_result.inline_size;
+ if (!trailing_collapsible_space_.has_value() ||
+ trailing_collapsible_space_->item_result != &item_result) {
+ trailing_collapsible_space_.emplace();
+ trailing_collapsible_space_->item_result = &item_result;
+ if (item_result.end_offset - 1 > item_result.start_offset) {
+ trailing_collapsible_space_->collapsed_shape_result =
+ TruncateLineEndResult(item_result, item_result.end_offset - 1);
+ }
}
return;
}
+
+ trailing_collapsible_space_.reset();
}
-void NGLineBreaker::AppendHyphen(const NGInlineItem& item,
- NGLineInfo* line_info) {
+void NGLineBreaker::AppendHyphen(const NGInlineItem& item) {
DCHECK(item.Style());
const ComputedStyle& style = *item.Style();
TextDirection direction = style.Direction();
String hyphen_string = style.HyphenString();
- hyphen_string.Ensure16Bit();
- HarfBuzzShaper shaper(hyphen_string.Characters16(), hyphen_string.length());
+ HarfBuzzShaper shaper(hyphen_string);
scoped_refptr<ShapeResult> hyphen_result =
shaper.Shape(&style.GetFont(), direction);
NGTextFragmentBuilder builder(node_, constraint_space_.GetWritingMode());
builder.SetText(item.GetLayoutObject(), hyphen_string, &style,
/* is_ellipsis_style */ false, std::move(hyphen_result));
- SetLineEndFragment(builder.ToTextFragment(), line_info);
+ SetLineEndFragment(builder.ToTextFragment());
}
// Measure control items; new lines and tab, that are similar to text, affect
// layout, but do not need shaping/painting.
-NGLineBreaker::LineBreakState NGLineBreaker::HandleControlItem(
- const NGInlineItem& item,
- LineBreakState state,
- NGLineInfo* line_info) {
+void NGLineBreaker::HandleControlItem(const NGInlineItem& item) {
DCHECK_EQ(item.Length(), 1u);
- line_.should_create_line_box = true;
UChar character = Text()[item.StartOffset()];
switch (character) {
case kNewlineCharacter: {
- NGInlineItemResult* item_result = AddItem(item, &line_info->Results());
+ NGInlineItemResult* item_result = AddItem(item);
+ item_result->should_create_line_box = true;
item_result->has_only_trailing_spaces = true;
- line_.is_after_forced_break = true;
- line_info->SetIsLastLine(true);
- state = LineBreakState::kDone;
+ is_after_forced_break_ = true;
+ line_info_->SetIsLastLine(true);
+ state_ = LineBreakState::kDone;
break;
}
case kTabulationCharacter: {
- NGInlineItemResult* item_result = AddItem(item, &line_info->Results());
+ NGInlineItemResult* item_result = AddItem(item);
+ item_result->should_create_line_box = true;
DCHECK(item.Style());
const ComputedStyle& style = *item.Style();
const Font& font = style.GetFont();
- item_result->inline_size =
- font.TabWidth(style.GetTabSize(), line_.position);
- line_.position += item_result->inline_size;
+ item_result->inline_size = font.TabWidth(style.GetTabSize(), position_);
+ position_ += item_result->inline_size;
item_result->has_only_trailing_spaces =
- state == LineBreakState::kTrailing;
+ state_ == LineBreakState::kTrailing;
ComputeCanBreakAfter(item_result);
break;
}
case kZeroWidthSpaceCharacter: {
// <wbr> tag creates break opportunities regardless of auto_wrap.
- NGInlineItemResult* item_result = AddItem(item, &line_info->Results());
+ NGInlineItemResult* item_result = AddItem(item);
+ item_result->should_create_line_box = true;
item_result->can_break_after = true;
break;
}
@@ -562,15 +713,10 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleControlItem(
break;
}
MoveToNextOf(item);
- return state;
}
-NGLineBreaker::LineBreakState NGLineBreaker::HandleBidiControlItem(
- const NGInlineItem& item,
- LineBreakState state,
- NGLineInfo* line_info) {
+void NGLineBreaker::HandleBidiControlItem(const NGInlineItem& item) {
DCHECK_EQ(item.Length(), 1u);
- NGInlineItemResults* item_results = &line_info->Results();
// Bidi control characters have enter/exit semantics. Handle "enter"
// characters simialr to open-tag, while "exit" (pop) characters similar to
@@ -579,34 +725,33 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleBidiControlItem(
bool is_pop = character == kPopDirectionalIsolateCharacter ||
character == kPopDirectionalFormattingCharacter;
if (is_pop) {
- if (!item_results->IsEmpty()) {
- NGInlineItemResult* item_result = AddItem(item, item_results);
- NGInlineItemResult* last = &(*item_results)[item_results->size() - 2];
+ if (!item_results_->IsEmpty()) {
+ NGInlineItemResult* item_result = AddItem(item);
+ NGInlineItemResult* last = &(*item_results_)[item_results_->size() - 2];
item_result->can_break_after = last->can_break_after;
last->can_break_after = false;
} else {
- AddItem(item, item_results);
+ AddItem(item);
}
} else {
- if (state == LineBreakState::kTrailing &&
- CanBreakAfterLast(*item_results)) {
- line_info->SetIsLastLine(false);
+ if (state_ == LineBreakState::kTrailing &&
+ CanBreakAfterLast(*item_results_)) {
+ line_info_->SetIsLastLine(false);
MoveToNextOf(item);
- return LineBreakState::kDone;
+ state_ = LineBreakState::kDone;
+ return;
}
- NGInlineItemResult* item_result = AddItem(item, item_results);
+ NGInlineItemResult* item_result = AddItem(item);
DCHECK(!item_result->can_break_after);
}
MoveToNextOf(item);
- return state;
}
-void NGLineBreaker::HandleAtomicInline(const NGInlineItem& item,
- NGLineInfo* line_info) {
+void NGLineBreaker::HandleAtomicInline(const NGInlineItem& item) {
DCHECK_EQ(item.Type(), NGInlineItem::kAtomicInline);
- line_.should_create_line_box = true;
- NGInlineItemResult* item_result = AddItem(item, &line_info->Results());
+ NGInlineItemResult* item_result = AddItem(item);
+ item_result->should_create_line_box = true;
// When we're just computing min/max content sizes, we can skip the full
// layout and just compute those sizes. On the other hand, for regular
// layout we need to do the full layout and get the layout result.
@@ -616,8 +761,8 @@ void NGLineBreaker::HandleAtomicInline(const NGInlineItem& item,
item_result->layout_result =
NGBlockNode(ToLayoutBox(item.GetLayoutObject()))
.LayoutAtomicInline(constraint_space_,
- line_info->LineStyle().GetFontBaseline(),
- line_info->UseFirstLineStyle());
+ line_info_->LineStyle().GetFontBaseline(),
+ line_info_->UseFirstLineStyle());
DCHECK(item_result->layout_result->PhysicalFragment());
item_result->inline_size =
@@ -636,12 +781,18 @@ void NGLineBreaker::HandleAtomicInline(const NGInlineItem& item,
}
DCHECK(item.Style());
+ const ComputedStyle& style = *item.Style();
+ bool is_flipped_lines = style.IsFlippedLinesWritingMode();
item_result->margins =
- ComputeMarginsForVisualContainer(constraint_space_, *item.Style());
- item_result->padding = ComputePadding(constraint_space_, *item.Style());
+ NGLineBoxStrut(ComputeMarginsForVisualContainer(constraint_space_, style),
+ is_flipped_lines);
+ item_result->padding = NGLineBoxStrut(
+ ComputePadding(constraint_space_, style), is_flipped_lines);
item_result->inline_size += item_result->margins.InlineSum();
- line_.position += item_result->inline_size;
+ if (state_ == LineBreakState::kLeading)
+ state_ = LineBreakState::kContinue;
+ position_ += item_result->inline_size;
ComputeCanBreakAfter(item_result);
MoveToNextOf(item);
}
@@ -660,9 +811,7 @@ void NGLineBreaker::HandleAtomicInline(const NGInlineItem& item,
// We have this check if there are already UnpositionedFloats as we aren't
// allowed to position a float "above" another float which has come before us
// in the document.
-void NGLineBreaker::HandleFloat(const NGInlineItem& item,
- NGLineInfo* line_info,
- NGInlineItemResult* item_result) {
+void NGLineBreaker::HandleFloat(const NGInlineItem& item) {
// When rewind occurs, an item may be handled multiple times.
// Since floats are put into a separate list, avoid handling same floats
// twice.
@@ -672,18 +821,12 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
// Additionally, we need to skip floats if we're retrying a line after a
// fragmentainer break. In that case the floats associated with this line will
// already have been processed.
+ NGInlineItemResult* item_result = AddItem(item);
ComputeCanBreakAfter(item_result);
MoveToNextOf(item);
if (item_index_ <= handled_floats_end_item_index_ || ignore_floats_)
return;
- // Floats need to know the current line width to determine whether to put it
- // into the current line or to the next line. Remove trailing spaces if this
- // float is trailing, because whitespace should be collapsed across floats,
- // and this logic requires the width after trailing spaces are collapsed.
- if (IsTrailing(item, *line_info))
- RemoveTrailingCollapsibleSpace(line_info);
-
NGBlockNode node(ToLayoutBox(item.GetLayoutObject()));
const ComputedStyle& float_style = node.Style();
@@ -706,7 +849,21 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
margins.InlineSum())
.ClampNegativeToZero();
- LayoutUnit bfc_block_offset = line_.line_opportunity.bfc_block_offset;
+ LayoutUnit bfc_block_offset = line_opportunity_.bfc_block_offset;
+
+ bool can_fit_float =
+ position_ + inline_margin_size <=
+ line_opportunity_.AvailableFloatInlineSize().AddEpsilon();
+ if (!can_fit_float) {
+ // Floats need to know the current line width to determine whether to put it
+ // into the current line or to the next line. Trailing spaces will be
+ // removed if this line breaks here because they should be collapsed across
+ // floats, but they are still included in the current line position at this
+ // point. Exclude it when computing whether this float can fit or not.
+ can_fit_float =
+ position_ + inline_margin_size - TrailingCollapsibleSpaceWidth() <=
+ line_opportunity_.AvailableFloatInlineSize().AddEpsilon();
+ }
// The float should be positioned after the current line if:
// - It can't fit within the non-shape area. (Assuming the current position
@@ -717,7 +874,7 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
// unpositioned_floats to manually adjust the min/max-content size after
// the line breaker has run).
bool float_after_line =
- !line_.CanFloatFit(inline_margin_size) ||
+ !can_fit_float ||
exclusion_space_->LastFloatBlockStart() > bfc_block_offset ||
exclusion_space_->ClearanceOffset(float_style.Clear()) >
bfc_block_offset ||
@@ -743,11 +900,10 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
DCHECK_EQ(bfc_block_offset, opportunity.rect.BlockStartOffset());
- line_.line_opportunity = opportunity.ComputeLineLayoutOpportunity(
- constraint_space_, line_.line_opportunity.line_block_size,
- LayoutUnit());
+ line_opportunity_ = opportunity.ComputeLineLayoutOpportunity(
+ constraint_space_, line_opportunity_.line_block_size, LayoutUnit());
- DCHECK_GE(line_.AvailableWidth(), LayoutUnit());
+ DCHECK_GE(AvailableWidth(), LayoutUnit());
}
}
@@ -762,38 +918,42 @@ bool NGLineBreaker::ComputeOpenTagResult(
if (item.ShouldCreateBoxFragment() &&
(style.HasBorder() || style.HasPadding() ||
(style.HasMargin() && item_result->has_edge))) {
- NGBoxStrut borders = ComputeBorders(constraint_space, style);
- NGBoxStrut paddings = ComputePadding(constraint_space, style);
- item_result->padding = paddings;
- item_result->borders_paddings_block_start =
- borders.block_start + paddings.block_start;
- item_result->borders_paddings_block_end =
- borders.block_end + paddings.block_end;
+ bool is_flipped_lines = style.IsFlippedLinesWritingMode();
+ NGLineBoxStrut borders = NGLineBoxStrut(
+ ComputeBorders(constraint_space, style), is_flipped_lines);
+ item_result->padding = NGLineBoxStrut(
+ ComputePadding(constraint_space, style), is_flipped_lines);
+ item_result->borders_paddings_line_over =
+ borders.line_over + item_result->padding.line_over;
+ item_result->borders_paddings_line_under =
+ borders.line_under + item_result->padding.line_under;
if (item_result->has_edge) {
- item_result->margins = ComputeMarginsForSelf(constraint_space, style);
+ item_result->margins = NGLineBoxStrut(
+ ComputeMarginsForSelf(constraint_space, style), is_flipped_lines);
item_result->inline_size = item_result->margins.inline_start +
- borders.inline_start + paddings.inline_start;
+ borders.inline_start +
+ item_result->padding.inline_start;
return true;
}
}
return false;
}
-void NGLineBreaker::HandleOpenTag(const NGInlineItem& item,
- NGInlineItemResult* item_result) {
+void NGLineBreaker::HandleOpenTag(const NGInlineItem& item) {
+ NGInlineItemResult* item_result = AddItem(item);
DCHECK(!item_result->can_break_after);
if (ComputeOpenTagResult(item, constraint_space_, item_result)) {
- line_.position += item_result->inline_size;
+ position_ += item_result->inline_size;
// While the spec defines "non-zero margins, padding, or borders" prevents
// line boxes to be zero-height, tests indicate that only inline direction
// of them do so. See should_create_line_box_.
// Force to create a box, because such inline boxes affect line heights.
- if (!line_.should_create_line_box &&
+ if (!item_result->should_create_line_box &&
(item_result->inline_size ||
(item_result->margins.inline_start && !in_line_height_quirks_mode_)))
- line_.should_create_line_box = true;
+ item_result->should_create_line_box = true;
}
DCHECK(item.Style());
@@ -802,24 +962,25 @@ void NGLineBreaker::HandleOpenTag(const NGInlineItem& item,
MoveToNextOf(item);
}
-void NGLineBreaker::HandleCloseTag(const NGInlineItem& item,
- NGInlineItemResults* item_results) {
- NGInlineItemResult* item_result = AddItem(item, item_results);
+void NGLineBreaker::HandleCloseTag(const NGInlineItem& item) {
+ NGInlineItemResult* item_result = AddItem(item);
item_result->has_edge = item.HasEndEdge();
if (item_result->has_edge) {
DCHECK(item.Style());
const ComputedStyle& style = *item.Style();
- item_result->margins = ComputeMarginsForSelf(constraint_space_, style);
+ bool is_flipped_lines = style.IsFlippedLinesWritingMode();
+ item_result->margins = NGLineBoxStrut(
+ ComputeMarginsForSelf(constraint_space_, style), is_flipped_lines);
NGBoxStrut borders = ComputeBorders(constraint_space_, style);
NGBoxStrut paddings = ComputePadding(constraint_space_, style);
item_result->inline_size = item_result->margins.inline_end +
borders.inline_end + paddings.inline_end;
- line_.position += item_result->inline_size;
+ position_ += item_result->inline_size;
- if (!line_.should_create_line_box &&
+ if (!item_result->should_create_line_box &&
(item_result->inline_size ||
(item_result->margins.inline_end && !in_line_height_quirks_mode_)))
- line_.should_create_line_box = true;
+ item_result->should_create_line_box = true;
}
DCHECK(item.GetLayoutObject() && item.GetLayoutObject()->Parent());
bool was_auto_wrap = auto_wrap_;
@@ -831,8 +992,8 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item,
// TODO(kojii): There should be a result before close tag, but there are cases
// that doesn't because of the way we handle trailing spaces. This needs to be
// revisited.
- if (item_results->size() >= 2) {
- NGInlineItemResult* last = &(*item_results)[item_results->size() - 2];
+ if (item_results_->size() >= 2) {
+ NGInlineItemResult* last = &(*item_results_)[item_results_->size() - 2];
if (was_auto_wrap == auto_wrap_) {
item_result->can_break_after = last->can_break_after;
last->can_break_after = false;
@@ -859,31 +1020,26 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item,
// Handles when the last item overflows.
// At this point, item_results does not fit into the current line, and there
// are no break opportunities in item_results.back().
-NGLineBreaker::LineBreakState NGLineBreaker::HandleOverflow(
- NGLineInfo* line_info) {
- return HandleOverflow(line_info, line_.AvailableWidth());
-}
-
-NGLineBreaker::LineBreakState NGLineBreaker::HandleOverflow(
- NGLineInfo* line_info,
- LayoutUnit available_width) {
- NGInlineItemResults* item_results = &line_info->Results();
- LayoutUnit width_to_rewind = line_.position - available_width;
+void NGLineBreaker::HandleOverflow() {
+ LayoutUnit available_width = AvailableWidthToFit();
+ LayoutUnit width_to_rewind = position_ - available_width;
DCHECK_GT(width_to_rewind, 0);
+ bool position_maybe_changed = false;
// Keep track of the shortest break opportunity.
unsigned break_before = 0;
// Search for a break opportunity that can fit.
- for (unsigned i = item_results->size(); i;) {
- NGInlineItemResult* item_result = &(*item_results)[--i];
+ for (unsigned i = item_results_->size(); i;) {
+ NGInlineItemResult* item_result = &(*item_results_)[--i];
// Try to break after this item.
- if (i < item_results->size() - 1 && item_result->can_break_after) {
+ if (i < item_results_->size() - 1 && item_result->can_break_after) {
if (width_to_rewind <= 0) {
- line_.position = available_width + width_to_rewind;
- Rewind(line_info, i + 1);
- return LineBreakState::kTrailing;
+ position_ = available_width + width_to_rewind;
+ Rewind(i + 1);
+ state_ = LineBreakState::kTrailing;
+ return;
}
break_before = i + 1;
}
@@ -900,33 +1056,32 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleOverflow(
LayoutUnit item_available_width =
std::min(-next_width_to_rewind, item_result->inline_size - 1);
SetCurrentStyle(*item.Style());
- BreakText(item_result, item, item_available_width, line_info);
+ BreakText(item_result, item, item_available_width);
#if DCHECK_IS_ON()
- item_result->CheckConsistency();
+ item_result->CheckConsistency(true);
#endif
+ // If BreakText() changed this item small enough to fit, break here.
if (item_result->inline_size <= item_available_width) {
DCHECK(item_result->end_offset < item.EndOffset());
DCHECK(item_result->can_break_after);
- DCHECK_LE(i + 1, item_results->size());
- if (i + 1 == item_results->size()) {
+ DCHECK_LE(i + 1, item_results_->size());
+ if (i + 1 == item_results_->size()) {
// If this is the last item, adjust states to accomodate the change.
- line_.position =
+ position_ =
available_width + next_width_to_rewind + item_result->inline_size;
- if (line_info->LineEndFragment())
- SetLineEndFragment(nullptr, line_info);
-#if DCHECK_IS_ON()
- LayoutUnit position_fast = line_.position;
- UpdatePosition(line_info->Results());
- DCHECK_EQ(line_.position, position_fast);
-#endif
+ if (line_info_->LineEndFragment())
+ SetLineEndFragment(nullptr);
+ DCHECK_EQ(position_, line_info_->ComputeWidth());
item_index_ = item_result->item_index;
offset_ = item_result->end_offset;
items_data_.AssertOffset(item_index_, offset_);
} else {
- Rewind(line_info, i + 1);
+ Rewind(i + 1);
}
- return LineBreakState::kTrailing;
+ state_ = LineBreakState::kTrailing;
+ return;
}
+ position_maybe_changed = true;
}
width_to_rewind = next_width_to_rewind;
@@ -937,65 +1092,81 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleOverflow(
if (break_anywhere_if_overflow_ && !override_break_anywhere_) {
override_break_anywhere_ = true;
break_iterator_.SetBreakType(LineBreakType::kBreakCharacter);
- Rewind(line_info, 0);
- return LineBreakState::kContinue;
+ if (!item_results_->IsEmpty())
+ Rewind(0);
+ state_ = LineBreakState::kLeading;
+ return;
}
// Let this line overflow.
// If there was a break opportunity, the overflow should stop there.
if (break_before) {
- Rewind(line_info, break_before);
- return LineBreakState::kTrailing;
+ Rewind(break_before);
+ state_ = LineBreakState::kTrailing;
+ return;
}
- return LineBreakState::kTrailing;
+ if (position_maybe_changed) {
+ UpdatePosition();
+ }
+
+ state_ = LineBreakState::kTrailing;
}
-void NGLineBreaker::Rewind(NGLineInfo* line_info, unsigned new_end) {
- NGInlineItemResults* item_results = &line_info->Results();
- DCHECK_LT(new_end, item_results->size());
+void NGLineBreaker::Rewind(unsigned new_end) {
+ NGInlineItemResults& item_results = *item_results_;
+ DCHECK_LT(new_end, item_results.size());
+
+ // Avoid rewinding floats if possible. They will be added back anyway while
+ // processing trailing items even when zero available width. Also this saves
+ // most cases where our support for rewinding positioned floats is not great
+ // yet (see below.)
+ while (item_results[new_end].item->Type() == NGInlineItem::kFloating) {
+ ++new_end;
+ if (new_end == item_results.size()) {
+ UpdatePosition();
+ return;
+ }
+ }
- // TODO(ikilpatrick): Add DCHECK that we never rewind past any floats.
+ // Because floats are added to |positioned_floats_| or |unpositioned_floats_|,
+ // rewinding them needs to remove from these lists too.
+ for (unsigned i = item_results.size(); i > new_end;) {
+ NGInlineItemResult& rewind = item_results[--i];
+ if (rewind.item->Type() == NGInlineItem::kFloating) {
+ NGBlockNode float_node(ToLayoutBox(rewind.item->GetLayoutObject()));
+ if (!RemoveUnpositionedFloat(unpositioned_floats_, float_node)) {
+ // TODO(kojii): We do not have mechanism to remove once positioned
+ // floats yet, and that rewinding them may lay it out twice. For now,
+ // prohibit rewinding positioned floats. This may results in incorrect
+ // layout, but still better than rewinding them.
+ new_end = i + 1;
+ if (new_end == item_results.size()) {
+ UpdatePosition();
+ return;
+ }
+ break;
+ }
+ }
+ }
if (new_end) {
// Use |results[new_end - 1].end_offset| because it may have been truncated
// and may not be equal to |results[new_end].start_offset|.
- MoveToNextOf((*item_results)[new_end - 1]);
+ MoveToNextOf(item_results[new_end - 1]);
} else {
// When rewinding all items, use |results[0].start_offset|.
- const NGInlineItemResult& first_remove = (*item_results)[new_end];
+ const NGInlineItemResult& first_remove = item_results[new_end];
item_index_ = first_remove.item_index;
offset_ = first_remove.start_offset;
}
- // TODO(kojii): Should we keep results for the next line? We don't need to
- // re-layout atomic inlines.
- item_results->Shrink(new_end);
-
- SetLineEndFragment(nullptr, line_info);
- UpdatePosition(line_info->Results());
-}
+ item_results.Shrink(new_end);
-// Returns the LayoutObject at the current index/offset.
-// This is to tie generated fragments to the source DOM node/LayoutObject for
-// paint invalidations, hit testing, etc.
-LayoutObject* NGLineBreaker::CurrentLayoutObject(
- const NGLineInfo& line_info) const {
- const Vector<NGInlineItem>& items = line_info.ItemsData().items;
- DCHECK_LE(item_index_, items.size());
- // Find the next item that has LayoutObject. Some items such as bidi controls
- // do not have LayoutObject.
- for (unsigned i = item_index_; i < items.size(); i++) {
- if (LayoutObject* layout_object = items[i].GetLayoutObject())
- return layout_object;
- }
- // Find the last item if there were no LayoutObject afterwards.
- for (unsigned i = item_index_; i--;) {
- if (LayoutObject* layout_object = items[i].GetLayoutObject())
- return layout_object;
- }
- NOTREACHED();
- return nullptr;
+ trailing_spaces_collapsed_ = false;
+ trailing_collapsible_space_.reset();
+ SetLineEndFragment(nullptr);
+ UpdatePosition();
}
void NGLineBreaker::SetCurrentStyle(const ComputedStyle& style) {
@@ -1058,7 +1229,7 @@ scoped_refptr<NGInlineBreakToken> NGLineBreaker::CreateBreakToken(
return NGInlineBreakToken::Create(node_);
return NGInlineBreakToken::Create(
node_, current_style_.get(), item_index_, offset_,
- ((line_.is_after_forced_break ? NGInlineBreakToken::kIsForcedBreak : 0) |
+ ((is_after_forced_break_ ? NGInlineBreakToken::kIsForcedBreak : 0) |
(line_info.UseFirstLineStyle() ? NGInlineBreakToken::kUseFirstLineStyle
: 0)),
std::move(state_stack));
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
index b0838c40a67..5f52e8c952e 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
@@ -5,11 +5,12 @@
#ifndef NGLineBreaker_h
#define NGLineBreaker_h
+#include "base/optional.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
-#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h"
#include "third_party/blink/renderer/platform/text/text_break_iterator.h"
#include "third_party/blink/renderer/platform/wtf/allocator.h"
@@ -43,12 +44,13 @@ class CORE_EXPORT NGLineBreaker {
NGContainerFragmentBuilder* container_builder,
NGExclusionSpace*,
unsigned handled_float_index,
+ const NGLineLayoutOpportunity&,
const NGInlineBreakToken* = nullptr);
~NGLineBreaker();
// Compute the next line break point and produces NGInlineItemResults for
// the line.
- bool NextLine(const NGLineLayoutOpportunity& line_opportunity, NGLineInfo*);
+ void NextLine(NGLineInfo*);
// Create an NGInlineBreakToken for the last line returned by NextLine().
scoped_refptr<NGInlineBreakToken> CreateBreakToken(
@@ -62,63 +64,20 @@ class CORE_EXPORT NGLineBreaker {
NGInlineItemResult*);
private:
- // This struct holds information for the current line.
- struct LineData {
- STACK_ALLOCATED();
-
- LineData(NGInlineNode node, const NGInlineBreakToken* break_token);
-
- // The current position from inline_start. Unlike NGInlineLayoutAlgorithm
- // that computes position in visual order, this position in logical order.
- LayoutUnit position;
-
- NGLineLayoutOpportunity line_opportunity;
-
- // True if this line is the "first formatted line".
- // https://www.w3.org/TR/CSS22/selector.html#first-formatted-line
- bool is_first_formatted_line;
-
- bool use_first_line_style;
-
- // We don't create "certain zero-height line boxes".
- // https://drafts.csswg.org/css2/visuren.html#phantom-line-box
- // Such line boxes do not prevent two margins being "adjoining", and thus
- // collapsing.
- // https://drafts.csswg.org/css2/box.html#collapsing-margins
- bool should_create_line_box = false;
-
- // Set when the line ended with a forced break. Used to setup the states for
- // the next line.
- bool is_after_forced_break = false;
-
- LayoutUnit AvailableWidth() const {
- return line_opportunity.AvailableInlineSize();
- }
- bool CanFit() const { return position <= AvailableWidth(); }
- bool CanFit(LayoutUnit extra) const {
- return position + extra <= AvailableWidth();
- }
- bool CanFloatFit(LayoutUnit extra) const {
- return position + extra <= line_opportunity.AvailableFloatInlineSize();
- }
- };
-
const String& Text() const { return items_data_.text_content; }
const Vector<NGInlineItem>& Items() const { return items_data_.items; }
- NGInlineItemResult* AddItem(const NGInlineItem&,
- unsigned end_offset,
- NGInlineItemResults*);
- NGInlineItemResult* AddItem(const NGInlineItem&, NGInlineItemResults*);
- void SetLineEndFragment(scoped_refptr<NGPhysicalTextFragment>, NGLineInfo*);
+ NGInlineItemResult* AddItem(const NGInlineItem&, unsigned end_offset);
+ NGInlineItemResult* AddItem(const NGInlineItem&);
+ void SetLineEndFragment(scoped_refptr<NGPhysicalTextFragment>);
void ComputeCanBreakAfter(NGInlineItemResult*) const;
- void BreakLine(NGLineInfo*);
+ void BreakLine();
- void PrepareNextLine(const NGLineLayoutOpportunity&, NGLineInfo*);
+ void PrepareNextLine();
- void UpdatePosition(const NGInlineItemResults&);
- void ComputeLineLocation(NGLineInfo*) const;
+ void UpdatePosition();
+ void ComputeLineLocation() const;
enum class LineBreakState {
// The line breaking is complete.
@@ -129,72 +88,81 @@ class CORE_EXPORT NGLineBreaker {
// when it is overflowing.
kTrailing,
- // The initial state. Looking for items to break the line.
+ // The initial state, until the first character is found.
+ kLeading,
+
+ // Looking for more items to fit into the current line.
kContinue,
};
- LineBreakState HandleText(const NGInlineItem&, LineBreakState, NGLineInfo*);
+ void HandleText(const NGInlineItem&);
void BreakText(NGInlineItemResult*,
const NGInlineItem&,
- LayoutUnit available_width,
- NGLineInfo*);
- LineBreakState HandleTrailingSpaces(const NGInlineItem&, NGLineInfo*);
- void RemoveTrailingCollapsibleSpace(NGLineInfo*);
- void AppendHyphen(const NGInlineItem& item, NGLineInfo*);
-
- LineBreakState HandleControlItem(const NGInlineItem&,
- LineBreakState,
- NGLineInfo*);
- LineBreakState HandleBidiControlItem(const NGInlineItem&,
- LineBreakState,
- NGLineInfo*);
- void HandleAtomicInline(const NGInlineItem&, NGLineInfo*);
- void HandleFloat(const NGInlineItem&, NGLineInfo*, NGInlineItemResult*);
-
- void HandleOpenTag(const NGInlineItem&, NGInlineItemResult*);
- void HandleCloseTag(const NGInlineItem&, NGInlineItemResults*);
-
- LineBreakState HandleOverflow(NGLineInfo*);
- LineBreakState HandleOverflow(NGLineInfo*, LayoutUnit available_width);
- void Rewind(NGLineInfo*, unsigned new_end);
-
- LayoutObject* CurrentLayoutObject(const NGLineInfo&) const;
+ LayoutUnit available_width);
+
+ scoped_refptr<ShapeResult> TruncateLineEndResult(
+ const NGInlineItemResult& item_result,
+ unsigned end_offset);
+ void UpdateShapeResult(NGInlineItemResult*);
+ scoped_refptr<ShapeResult> ShapeText(const NGInlineItem& item,
+ unsigned start,
+ unsigned end);
+
+ void HandleTrailingSpaces(const NGInlineItem&);
+ void RemoveTrailingCollapsibleSpace();
+ LayoutUnit TrailingCollapsibleSpaceWidth();
+ void ComputeTrailingCollapsibleSpace();
+
+ void AppendHyphen(const NGInlineItem& item);
+
+ void HandleControlItem(const NGInlineItem&);
+ void HandleBidiControlItem(const NGInlineItem&);
+ void HandleAtomicInline(const NGInlineItem&);
+ void HandleFloat(const NGInlineItem&);
+
+ void HandleOpenTag(const NGInlineItem&);
+ void HandleCloseTag(const NGInlineItem&);
+
+ void HandleOverflow();
+ void Rewind(unsigned new_end);
void SetCurrentStyle(const ComputedStyle&);
void MoveToNextOf(const NGInlineItem&);
void MoveToNextOf(const NGInlineItemResult&);
- void ComputeBaseDirection(const NGLineInfo&);
- bool IsTrailing(const NGInlineItem&, const NGLineInfo&) const;
+ void ComputeBaseDirection();
- LineData line_;
- NGInlineNode node_;
- const NGInlineItemsData& items_data_;
+ LayoutUnit AvailableWidth() const {
+ return line_opportunity_.AvailableInlineSize();
+ }
+ LayoutUnit AvailableWidthToFit() const {
+ return AvailableWidth().AddEpsilon();
+ }
- NGLineBreakerMode mode_;
- const NGConstraintSpace& constraint_space_;
- Vector<NGPositionedFloat>* positioned_floats_;
- Vector<scoped_refptr<NGUnpositionedFloat>>* unpositioned_floats_;
- NGContainerFragmentBuilder* container_builder_; /* May be nullptr */
- NGExclusionSpace* exclusion_space_;
- scoped_refptr<const ComputedStyle> current_style_;
+ // These fields are the output of the current line.
+ // NGInlineItemResults is a pointer because the move operation is not cheap
+ // due to its inline buffer.
+ NGLineInfo* line_info_ = nullptr;
+ NGInlineItemResults* item_results_ = nullptr;
+ // Represents the current offset of the input.
+ LineBreakState state_;
unsigned item_index_ = 0;
unsigned offset_ = 0;
- LazyLineBreakIterator break_iterator_;
- HarfBuzzShaper shaper_;
- ShapeResultSpacing<String> spacing_;
- bool previous_line_had_forced_break_ = false;
- const Hyphenation* hyphenation_ = nullptr;
- // Keep track of handled float items. See HandleFloat().
- unsigned handled_floats_end_item_index_;
+ // The current position from inline_start. Unlike NGInlineLayoutAlgorithm
+ // that computes position in visual order, this position in logical order.
+ LayoutUnit position_;
+ NGLineLayoutOpportunity line_opportunity_;
- // The current base direction for the bidi algorithm.
- // This is copied from NGInlineNode, then updated after each forced line break
- // if 'unicode-bidi: plaintext'.
- TextDirection base_direction_;
+ NGInlineNode node_;
+
+ // True if this line is the "first formatted line".
+ // https://www.w3.org/TR/CSS22/selector.html#first-formatted-line
+ bool is_first_formatted_line_ = false;
+
+ bool use_first_line_style_ = false;
// True when current box allows line wrapping.
bool auto_wrap_ = false;
@@ -214,7 +182,49 @@ class CORE_EXPORT NGLineBreaker {
// https://quirks.spec.whatwg.org/#the-line-height-calculation-quirk
bool in_line_height_quirks_mode_ = false;
+ // True when the line we are breaking has a list marker.
+ bool has_list_marker_ = false;
+
+ // True if trailing collapsible spaces have been collapsed.
+ bool trailing_spaces_collapsed_ = false;
+
+ // Set when the line ended with a forced break. Used to setup the states for
+ // the next line.
+ bool is_after_forced_break_ = false;
+
bool ignore_floats_ = false;
+
+ const NGInlineItemsData& items_data_;
+
+ NGLineBreakerMode mode_;
+ const NGConstraintSpace& constraint_space_;
+ Vector<NGPositionedFloat>* positioned_floats_;
+ Vector<scoped_refptr<NGUnpositionedFloat>>* unpositioned_floats_;
+ NGContainerFragmentBuilder* container_builder_; /* May be nullptr */
+ NGExclusionSpace* exclusion_space_;
+ scoped_refptr<const ComputedStyle> current_style_;
+
+ LazyLineBreakIterator break_iterator_;
+ HarfBuzzShaper shaper_;
+ ShapeResultSpacing<String> spacing_;
+ bool previous_line_had_forced_break_ = false;
+ const Hyphenation* hyphenation_ = nullptr;
+
+ // Cache the result of |ComputeTrailingCollapsibleSpace| to avoid shaping
+ // multiple times.
+ struct TrailingCollapsibleSpace {
+ NGInlineItemResult* item_result;
+ scoped_refptr<const ShapeResult> collapsed_shape_result;
+ };
+ base::Optional<TrailingCollapsibleSpace> trailing_collapsible_space_;
+
+ // Keep track of handled float items. See HandleFloat().
+ unsigned handled_floats_end_item_index_;
+
+ // The current base direction for the bidi algorithm.
+ // This is copied from NGInlineNode, then updated after each forced line break
+ // if 'unicode-bidi: plaintext'.
+ TextDirection base_direction_;
};
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc
index 30c5402b812..1a6baf2a451 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc
@@ -48,13 +48,16 @@ class NGLineBreakerTest : public NGBaseLayoutAlgorithmTest {
Vector<NGInlineItemResults> lines;
NGExclusionSpace exclusion_space;
NGLineLayoutOpportunity line_opportunity(available_width);
- NGLineInfo line_info;
while (!break_token || !break_token->IsFinished()) {
+ NGLineInfo line_info;
NGLineBreaker line_breaker(node, NGLineBreakerMode::kContent, *space,
&positioned_floats, &unpositioned_floats,
/* container_builder */ nullptr,
- &exclusion_space, 0u, break_token.get());
- if (!line_breaker.NextLine(line_opportunity, &line_info))
+ &exclusion_space, 0u, line_opportunity,
+ break_token.get());
+ line_breaker.NextLine(&line_info);
+
+ if (line_info.Results().IsEmpty())
break;
break_token = line_breaker.CreateBreakToken(line_info, nullptr);
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.cc
index b83d812d685..7b773abd116 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.cc
@@ -25,8 +25,10 @@ NGLineHeightMetrics::NGLineHeightMetrics(const FontMetrics& font_metrics,
void NGLineHeightMetrics::Initialize(const FontMetrics& font_metrics,
FontBaseline baseline_type) {
- ascent = font_metrics.FixedAscent(baseline_type);
- descent = font_metrics.FixedDescent(baseline_type);
+ // TODO(kojii): In future, we'd like to use LayoutUnit metrics to support
+ // sub-CSS-pixel layout.
+ ascent = LayoutUnit(font_metrics.Ascent(baseline_type));
+ descent = LayoutUnit(font_metrics.Descent(baseline_type));
}
void NGLineHeightMetrics::AddLeading(LayoutUnit line_height) {
@@ -49,4 +51,12 @@ void NGLineHeightMetrics::Unite(const NGLineHeightMetrics& other) {
descent = std::max(descent, other.descent);
}
+void NGLineHeightMetrics::operator+=(const NGLineHeightMetrics& other) {
+ DCHECK(ascent != LayoutUnit::Min() && descent != LayoutUnit::Min());
+ DCHECK(other.ascent != LayoutUnit::Min() &&
+ other.descent != LayoutUnit::Min());
+ ascent += other.ascent;
+ descent += other.descent;
+}
+
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h
index 978c170044e..e5ef21b8580 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h
@@ -33,6 +33,13 @@ struct NGLineHeightMetrics {
bool IsEmpty() const { return ascent == LayoutUnit::Min(); }
+ bool operator==(const NGLineHeightMetrics& other) const {
+ return ascent == other.ascent && descent == other.descent;
+ }
+ bool operator!=(const NGLineHeightMetrics& other) const {
+ return !operator==(other);
+ }
+
// Add the leading. Half the leading is added to ascent and descent each.
// https://drafts.csswg.org/css2/visudet.html#leading
void AddLeading(LayoutUnit line_height);
@@ -43,6 +50,8 @@ struct NGLineHeightMetrics {
// Unite a metrics for an inline box to a metrics for a line box.
void Unite(const NGLineHeightMetrics&);
+ void operator+=(const NGLineHeightMetrics&);
+
// Ascent and descent of glyphs, or synthesized for replaced elements.
// Then united to compute 'text-top' and 'text-bottom' of line boxes.
LayoutUnit ascent;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
index ae76600c718..919cb689b26 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
@@ -7,7 +7,7 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h"
#include "third_party/blink/renderer/platform/fonts/font_baseline.h"
-#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h"
namespace blink {
@@ -54,7 +54,7 @@ LayoutUnit NGLineTruncator::TruncateLine(
font_data && font_data->GlyphForCharacter(kHorizontalEllipsisCharacter)
? String(&kHorizontalEllipsisCharacter, 1)
: String(u"...");
- HarfBuzzShaper shaper(ellipsis_text.Characters16(), ellipsis_text.length());
+ HarfBuzzShaper shaper(ellipsis_text);
scoped_refptr<ShapeResult> ellipsis_shape_result =
shaper.Shape(&font, line_direction_);
LayoutUnit ellipsis_width = ellipsis_shape_result->SnappedWidth();
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
index e341ddffdae..3187df368dd 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
@@ -5,48 +5,17 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
namespace blink {
-namespace {
-
-static const NGPhysicalFragment* LastLogicalLeafExceptLinebreakInternal(
- const NGPhysicalFragment& runner,
- TextDirection direction) {
- if (runner.IsText()) {
- if (ToNGPhysicalTextFragment(runner).IsLineBreak())
- return nullptr;
- return &runner;
- }
- if (!runner.IsContainer() || runner.IsBlockLayoutRoot())
- return &runner;
- const auto& children = ToNGPhysicalContainerFragment(runner).Children();
- for (size_t i = 0; i < children.size(); i++) {
- // TODO(xiaochengh): This isn't correct for mixed Bidi. Fix it. Besides, we
- // should compute and store it during layout.
- // We want a logical last child in a line.
- const size_t index =
- direction == TextDirection::kLtr ? (children.size() - 1 - i) : i;
- const NGPhysicalFragment* child = children[index].get();
- DCHECK(child);
- if (const NGPhysicalFragment* candidate =
- LastLogicalLeafExceptLinebreakInternal(*child, direction))
- return candidate;
- }
- return nullptr;
-}
-
-} // namespace
-
NGPhysicalLineBoxFragment::NGPhysicalLineBoxFragment(
const ComputedStyle& style,
NGStyleVariant style_variant,
NGPhysicalSize size,
Vector<scoped_refptr<NGPhysicalFragment>>& children,
- const NGPhysicalOffsetRect& contents_visual_rect,
- const NGPhysicalOffsetRect& scrollable_overflow,
+ const NGPhysicalOffsetRect& contents_ink_overflow,
const NGLineHeightMetrics& metrics,
TextDirection base_direction,
scoped_refptr<NGBreakToken> break_token)
@@ -57,22 +26,43 @@ NGPhysicalLineBoxFragment::NGPhysicalLineBoxFragment(
kFragmentLineBox,
0,
children,
- contents_visual_rect,
+ contents_ink_overflow,
std::move(break_token)),
- scrollable_overflow_(scrollable_overflow),
metrics_(metrics) {
base_direction_ = static_cast<unsigned>(base_direction);
}
-LayoutUnit NGPhysicalLineBoxFragment::BaselinePosition(FontBaseline) const {
+NGLineHeightMetrics NGPhysicalLineBoxFragment::BaselineMetrics(
+ FontBaseline) const {
// TODO(kojii): Computing other baseline types than the used one is not
// implemented yet.
// TODO(kojii): We might need locale/script to look up OpenType BASE table.
- return metrics_.ascent;
+ return metrics_;
}
-NGPhysicalOffsetRect NGPhysicalLineBoxFragment::VisualRectWithContents() const {
- return ContentsVisualRect();
+NGPhysicalOffsetRect NGPhysicalLineBoxFragment::InkOverflow() const {
+ return ContentsInkOverflow();
+}
+
+NGPhysicalOffsetRect NGPhysicalLineBoxFragment::ScrollableOverflow(
+ const ComputedStyle* container_style,
+ NGPhysicalSize container_physical_size) const {
+ WritingMode container_writing_mode = container_style->GetWritingMode();
+ TextDirection container_direction = container_style->Direction();
+ NGPhysicalOffsetRect overflow({}, Size());
+ for (const auto& child : Children()) {
+ NGPhysicalOffsetRect child_scroll_overflow = child->ScrollableOverflow();
+ child_scroll_overflow.offset += child->Offset();
+ // If child has the same style as parent, parent will compute relative
+ // offset.
+ if (&child->Style() != container_style) {
+ child_scroll_overflow.offset +=
+ ComputeRelativeOffset(child->Style(), container_writing_mode,
+ container_direction, container_physical_size);
+ }
+ overflow.Unite(child_scroll_overflow);
+ }
+ return overflow;
}
const NGPhysicalFragment* NGPhysicalLineBoxFragment::FirstLogicalLeaf() const {
@@ -115,13 +105,6 @@ const NGPhysicalFragment* NGPhysicalLineBoxFragment::LastLogicalLeaf() const {
return runner;
}
-const NGPhysicalFragment*
-NGPhysicalLineBoxFragment::LastLogicalLeafIgnoringLineBreak() const {
- if (Children().IsEmpty())
- return nullptr;
- return LastLogicalLeafExceptLinebreakInternal(*this, this->BaseDirection());
-}
-
bool NGPhysicalLineBoxFragment::HasSoftWrapToNextLine() const {
DCHECK(BreakToken());
DCHECK(BreakToken()->IsInlineType());
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h
index b92b910d2b3..7ca454dcb6b 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h
@@ -20,8 +20,7 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final
NGStyleVariant style_variant,
NGPhysicalSize size,
Vector<scoped_refptr<NGPhysicalFragment>>& children,
- const NGPhysicalOffsetRect& contents_visual_rect,
- const NGPhysicalOffsetRect& scrollable_overflow,
+ const NGPhysicalOffsetRect& contents_ink_overflow,
const NGLineHeightMetrics&,
TextDirection base_direction,
scoped_refptr<NGBreakToken> break_token = nullptr);
@@ -36,23 +35,23 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final
}
// Compute baseline for the specified baseline type.
- LayoutUnit BaselinePosition(FontBaseline) const;
+ NGLineHeightMetrics BaselineMetrics(FontBaseline) const;
- // VisualRect of itself including contents, in the local coordinate.
- NGPhysicalOffsetRect VisualRectWithContents() const;
+ // Ink overflow of itself including contents, in the local coordinate.
+ NGPhysicalOffsetRect InkOverflow() const;
// Scrollable overflow. including contents, in the local coordinate.
- NGPhysicalOffsetRect ScrollableOverflow() const {
- return scrollable_overflow_;
- }
+ // ScrollableOverflow is not precomputed/cached because it cannot be computed
+ // when LineBox is generated because it needs container dimensions to
+ // resolve relative position of its children.
+ NGPhysicalOffsetRect ScrollableOverflow(
+ const ComputedStyle* container_style,
+ NGPhysicalSize container_physical_size) const;
// Returns the first/last leaf fragment in the line in logical order. Returns
// nullptr if the line box is empty.
const NGPhysicalFragment* FirstLogicalLeaf() const;
const NGPhysicalFragment* LastLogicalLeaf() const;
- // Returns the last leaf fragment in the line in logical order except line
- // break. Returns nullptr if such fragment doesn't exist.
- const NGPhysicalFragment* LastLogicalLeafIgnoringLineBreak() const;
// Whether the content soft-wraps to the next line.
bool HasSoftWrapToNextLine() const;
@@ -60,12 +59,11 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final
scoped_refptr<NGPhysicalFragment> CloneWithoutOffset() const {
Vector<scoped_refptr<NGPhysicalFragment>> children_copy(children_);
return base::AdoptRef(new NGPhysicalLineBoxFragment(
- Style(), StyleVariant(), size_, children_copy, contents_visual_rect_,
- scrollable_overflow_, metrics_, BaseDirection(), break_token_));
+ Style(), StyleVariant(), size_, children_copy, contents_ink_overflow_,
+ metrics_, BaseDirection(), break_token_));
}
private:
- NGPhysicalOffsetRect scrollable_overflow_;
NGLineHeightMetrics metrics_;
};
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc
index 331fb87d70b..cd44fd8ea05 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc
@@ -59,7 +59,6 @@ TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafInSimpleText) {
"</div>");
EXPECT_TEXT_FRAGMENT("foo", GetLineBox()->FirstLogicalLeaf());
EXPECT_TEXT_FRAGMENT("bar", GetLineBox()->LastLogicalLeaf());
- EXPECT_TEXT_FRAGMENT("bar", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
}
TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafInRtlText) {
@@ -70,7 +69,6 @@ TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafInRtlText) {
"</bdo>");
EXPECT_TEXT_FRAGMENT("foo", GetLineBox()->FirstLogicalLeaf());
EXPECT_TEXT_FRAGMENT("bar", GetLineBox()->LastLogicalLeaf());
- EXPECT_TEXT_FRAGMENT("bar", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
}
TEST_F(NGPhysicalLineBoxFragmentTest,
@@ -83,7 +81,6 @@ TEST_F(NGPhysicalLineBoxFragmentTest,
"</div>");
EXPECT_TEXT_FRAGMENT("f", GetLineBox()->FirstLogicalLeaf());
EXPECT_TEXT_FRAGMENT("r", GetLineBox()->LastLogicalLeaf());
- EXPECT_TEXT_FRAGMENT("r", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
}
TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafWithInlineBlock) {
@@ -95,26 +92,12 @@ TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafWithInlineBlock) {
"</div>");
EXPECT_BOX_FRAGMENT("foo", GetLineBox()->FirstLogicalLeaf());
EXPECT_BOX_FRAGMENT("baz", GetLineBox()->LastLogicalLeaf());
- EXPECT_BOX_FRAGMENT("baz", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
}
TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafWithImages) {
SetBodyInnerHTML("<div id=root><img id=img1>foo<img id=img2></div>");
EXPECT_BOX_FRAGMENT("img1", GetLineBox()->FirstLogicalLeaf());
EXPECT_BOX_FRAGMENT("img2", GetLineBox()->LastLogicalLeaf());
- EXPECT_BOX_FRAGMENT("img2", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
-}
-
-TEST_F(NGPhysicalLineBoxFragmentTest, LastLogicalLeafSoftWrap) {
- SetBodyInnerHTML("<div id=root style='width: 2em'>foo bar</div>");
- EXPECT_TEXT_FRAGMENT("foo", GetLineBox()->LastLogicalLeaf());
- EXPECT_TEXT_FRAGMENT("foo", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
-}
-
-TEST_F(NGPhysicalLineBoxFragmentTest, LastLogicalLeafHardWrap) {
- SetBodyInnerHTML("<div id=root>foo<br>bar</div>");
- EXPECT_TEXT_FRAGMENT("\n", GetLineBox()->LastLogicalLeaf());
- EXPECT_TEXT_FRAGMENT("foo", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
}
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
index d75f591cc0f..7c705969eb7 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
@@ -58,24 +58,31 @@ LayoutUnit NGPhysicalTextFragment::InlinePositionForOffset(
AdjustMidCluster::kToEnd);
}
-NGPhysicalOffsetRect NGPhysicalTextFragment::LocalRect(
- unsigned start_offset,
- unsigned end_offset) const {
+std::pair<LayoutUnit, LayoutUnit>
+NGPhysicalTextFragment::LineLeftAndRightForOffsets(unsigned start_offset,
+ unsigned end_offset) const {
DCHECK_LE(start_offset, end_offset);
DCHECK_GE(start_offset, start_offset_);
DCHECK_LE(end_offset, end_offset_);
- LayoutUnit start_position = InlinePositionForOffset(
+ const LayoutUnit start_position = InlinePositionForOffset(
start_offset, LayoutUnit::FromFloatFloor, AdjustMidCluster::kToStart);
- LayoutUnit end_position = InlinePositionForOffset(
+ const LayoutUnit end_position = InlinePositionForOffset(
end_offset, LayoutUnit::FromFloatCeil, AdjustMidCluster::kToEnd);
// Swap positions if RTL.
- if (UNLIKELY(start_position > end_position))
- std::swap(start_position, end_position);
-
- LayoutUnit inline_size = end_position - start_position;
+ return (UNLIKELY(start_position > end_position))
+ ? std::make_pair(end_position, start_position)
+ : std::make_pair(start_position, end_position);
+}
+NGPhysicalOffsetRect NGPhysicalTextFragment::LocalRect(
+ unsigned start_offset,
+ unsigned end_offset) const {
+ LayoutUnit start_position, end_position;
+ std::tie(start_position, end_position) =
+ LineLeftAndRightForOffsets(start_offset, end_offset);
+ const LayoutUnit inline_size = end_position - start_position;
switch (LineOrientation()) {
case NGLineOrientation::kHorizontal:
return {{start_position, LayoutUnit()}, {inline_size, Size().height}};
@@ -89,23 +96,24 @@ NGPhysicalOffsetRect NGPhysicalTextFragment::LocalRect(
return {};
}
-NGPhysicalOffsetRect NGPhysicalTextFragment::SelfVisualRect() const {
+NGPhysicalOffsetRect NGPhysicalTextFragment::SelfInkOverflow() const {
if (UNLIKELY(!shape_result_))
return LocalRect();
// Glyph bounds is in logical coordinate, origin at the alphabetic baseline.
- LayoutRect visual_rect = EnclosingLayoutRect(shape_result_->Bounds());
+ LayoutRect ink_overflow = EnclosingLayoutRect(shape_result_->Bounds());
// Make the origin at the logical top of this fragment.
const ComputedStyle& style = Style();
const Font& font = style.GetFont();
if (const SimpleFontData* font_data = font.PrimaryFont()) {
- visual_rect.SetY(visual_rect.Y() + font_data->GetFontMetrics().FixedAscent(
- kAlphabeticBaseline));
+ ink_overflow.SetY(
+ ink_overflow.Y() +
+ font_data->GetFontMetrics().FixedAscent(kAlphabeticBaseline));
}
if (float stroke_width = style.TextStrokeWidth()) {
- visual_rect.Inflate(LayoutUnit::FromFloatCeil(stroke_width / 2.0f));
+ ink_overflow.Inflate(LayoutUnit::FromFloatCeil(stroke_width / 2.0f));
}
if (style.GetTextEmphasisMark() != TextEmphasisMark::kNone) {
@@ -113,13 +121,13 @@ NGPhysicalOffsetRect NGPhysicalTextFragment::SelfVisualRect() const {
LayoutUnit(font.EmphasisMarkHeight(style.TextEmphasisMarkString()));
DCHECK_GT(emphasis_mark_height, LayoutUnit());
if (style.GetTextEmphasisLineLogicalSide() == LineLogicalSide::kOver) {
- visual_rect.ShiftYEdgeTo(
- std::min(visual_rect.Y(), -emphasis_mark_height));
+ ink_overflow.ShiftYEdgeTo(
+ std::min(ink_overflow.Y(), -emphasis_mark_height));
} else {
LayoutUnit logical_height =
style.IsHorizontalWritingMode() ? Size().height : Size().width;
- visual_rect.ShiftMaxYEdgeTo(
- std::max(visual_rect.MaxY(), logical_height + emphasis_mark_height));
+ ink_overflow.ShiftMaxYEdgeTo(
+ std::max(ink_overflow.MaxY(), logical_height + emphasis_mark_height));
}
}
@@ -129,16 +137,16 @@ NGPhysicalOffsetRect NGPhysicalTextFragment::SelfVisualRect() const {
LayoutRectOutsets(text_shadow->RectOutsetsIncludingOriginal()),
style.GetWritingMode());
text_shadow_logical_outsets.ClampNegativeToZero();
- visual_rect.Expand(text_shadow_logical_outsets);
+ ink_overflow.Expand(text_shadow_logical_outsets);
}
- visual_rect = LayoutRect(EnclosingIntRect(visual_rect));
+ ink_overflow = LayoutRect(EnclosingIntRect(ink_overflow));
// Uniting the frame rect ensures that non-ink spaces such side bearings, or
// even space characters, are included in the visual rect for decorations.
- NGPhysicalOffsetRect local_visual_rect = ConvertToLocal(visual_rect);
- local_visual_rect.Unite(LocalRect());
- return local_visual_rect;
+ NGPhysicalOffsetRect local_ink_overflow = ConvertToLocal(ink_overflow);
+ local_ink_overflow.Unite(LocalRect());
+ return local_ink_overflow;
}
scoped_refptr<NGPhysicalFragment> NGPhysicalTextFragment::TrimText(
@@ -184,9 +192,9 @@ unsigned NGPhysicalTextFragment::TextOffsetForPoint(
DCHECK(TextShapeResult());
const LayoutUnit& point_in_line_direction =
Style().IsHorizontalWritingMode() ? point.left : point.top;
- const bool include_partial_glyphs = true;
return TextShapeResult()->OffsetForPosition(point_in_line_direction.ToFloat(),
- include_partial_glyphs) +
+ IncludePartialGlyphs,
+ BreakGlyphs) +
StartOffset();
}
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
index f6921d10546..19758ce7666 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
@@ -41,11 +41,18 @@ enum class NGLineOrientation {
class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
public:
enum NGTextType {
+ // |text_| holds |NGNodeInlineData::text_content_|.
kNormalText,
kForcedLineBreak,
// Flow controls are not to be painted. In particular, a tabulation
// character and a soft-wrap opportunity.
kFlowControl,
+ kSymbolMarker,
+ // |text_| holds generated contents instead of |text_content_| in
+ // |NGNodeInlineData|, e.g. hyphen, and ellipsis.
+ // Note: Contents generated by CSS pseudo element, e.g. ::before, ::after,
+ // are not classified to this. See IsAnonymousText() for them.
+ kGeneratedText,
// When adding new values, make sure the bit size of |sub_type_| is large
// enough to store.
};
@@ -77,6 +84,8 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
}
NGTextType TextType() const { return static_cast<NGTextType>(sub_type_); }
+ // True if this is a generated text.
+ bool IsGeneratedText() const { return TextType() == kGeneratedText; }
// True if this is a forced line break.
bool IsLineBreak() const { return TextType() == kForcedLineBreak; }
// True if this is not for painting; i.e., a forced line break, a tabulation,
@@ -114,7 +123,7 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
// The visual bounding box that includes glpyh bounding box and CSS
// properties, in local coordinates.
- NGPhysicalOffsetRect SelfVisualRect() const;
+ NGPhysicalOffsetRect SelfInkOverflow() const;
NGTextEndEffect EndEffect() const {
return static_cast<NGTextEndEffect>(end_effect_);
@@ -142,6 +151,13 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
UBiDiLevel BidiLevel() const override;
TextDirection ResolvedDirection() const override;
+ // Compute line-relative coordinates for given offsets, this is not
+ // flow-relative:
+ // https://drafts.csswg.org/css-writing-modes-3/#line-directions
+ std::pair<LayoutUnit, LayoutUnit> LineLeftAndRightForOffsets(
+ unsigned start_offset,
+ unsigned end_offset) const;
+
private:
LayoutUnit InlinePositionForOffset(unsigned offset,
LayoutUnit (*round)(float),
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc
index 48cc659c404..8962d687d27 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc
@@ -35,6 +35,10 @@ class NGPhysicalTextFragmentTest : public NGLayoutTest {
}
return result;
}
+
+ static std::string GetText(const NGPhysicalTextFragment& fragment) {
+ return fragment.Text().ToString().Utf8().data();
+ }
};
TEST_F(NGPhysicalTextFragmentTest, LocalRect) {
@@ -155,6 +159,32 @@ TEST_F(NGPhysicalTextFragmentTest, BeforeAndAfterAreAnonymousText) {
EXPECT_TRUE(after.IsAnonymousText());
}
+TEST_F(NGPhysicalTextFragmentTest, Ellipsis) {
+ LoadAhem();
+ SetBodyInnerHTML(R"HTML(
+ <style>
+ #sample {
+ font: 10px/1 Ahem;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 4ch;
+ }
+ </style>
+ <p id="sample">abcdef</p>
+ )HTML");
+ auto text_fragments = CollectTextFragmentsInContainer("sample");
+ ASSERT_EQ(2u, text_fragments.size());
+
+ const NGPhysicalTextFragment& abcdef = *text_fragments[0];
+ const NGPhysicalTextFragment& ellipsis = *text_fragments[1];
+ EXPECT_EQ(NGPhysicalTextFragment::kNormalText, abcdef.TextType());
+ EXPECT_FALSE(abcdef.IsGeneratedText());
+ EXPECT_EQ(u8"abc", GetText(abcdef));
+ EXPECT_EQ(NGPhysicalTextFragment::kGeneratedText, ellipsis.TextType());
+ EXPECT_TRUE(ellipsis.IsGeneratedText());
+ EXPECT_EQ(u8"\u2026", GetText(ellipsis));
+}
+
TEST_F(NGPhysicalTextFragmentTest, ListMarkerIsAnonymousText) {
SetBodyInnerHTML(
"<ol style='list-style-position:inside'>"
@@ -170,6 +200,37 @@ TEST_F(NGPhysicalTextFragmentTest, ListMarkerIsAnonymousText) {
EXPECT_FALSE(text.IsAnonymousText());
}
+TEST_F(NGPhysicalTextFragmentTest, SoftHyphen) {
+ LoadAhem();
+ SetBodyInnerHTML(R"HTML(
+ <style>
+ #sample {
+ font: 10px/1 Ahem;
+ width: 3ch;
+ }
+ </style>
+ <p id="sample">abc&shy;def</p>
+ )HTML");
+ auto text_fragments = CollectTextFragmentsInContainer("sample");
+ ASSERT_EQ(3u, text_fragments.size());
+
+ const NGPhysicalTextFragment& abc = *text_fragments[0];
+ const NGPhysicalTextFragment& shy = *text_fragments[1];
+ const NGPhysicalTextFragment& def = *text_fragments[2];
+ EXPECT_EQ(NGPhysicalTextFragment::kNormalText, abc.TextType());
+ EXPECT_FALSE(abc.IsGeneratedText());
+ // Note: ShapeResult::RunInfo.width_ == 0 for U+00AD
+ EXPECT_EQ(u8"abc\u00AD", GetText(abc));
+ EXPECT_EQ(NGPhysicalTextFragment::kGeneratedText, shy.TextType());
+ EXPECT_TRUE(shy.IsGeneratedText());
+ // Note: |ComputedStyle::HypenString()| returns "-" or U+2010 based on
+ // glyph availability.
+ if (GetText(shy) != "-")
+ EXPECT_EQ(u8"\u2010", GetText(shy));
+ EXPECT_EQ(NGPhysicalTextFragment::kNormalText, def.TextType());
+ EXPECT_FALSE(def.IsGeneratedText());
+}
+
TEST_F(NGPhysicalTextFragmentTest, QuotationMarksAreAnonymousText) {
SetBodyInnerHTML("<div id=div><q>text</q></div>");
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc
index 9ee319a8d3c..c1cbefd220e 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc
@@ -40,6 +40,8 @@ void NGTextFragmentBuilder::SetItem(
const NGInlineItemsData& items_data,
NGInlineItemResult* item_result,
LayoutUnit line_height) {
+ DCHECK_NE(text_type, NGPhysicalTextFragment::kGeneratedText)
+ << "Please use SetText() instead.";
DCHECK(item_result);
DCHECK(item_result->item->Style());
@@ -65,7 +67,7 @@ void NGTextFragmentBuilder::SetText(
DCHECK(style);
DCHECK(shape_result);
- text_type_ = NGPhysicalTextFragment::kNormalText;
+ text_type_ = NGPhysicalTextFragment::kGeneratedText;
text_ = text;
item_index_ = std::numeric_limits<unsigned>::max();
start_offset_ = shape_result->StartIndexForResult();
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h
index 68efb30de15..df788efc4cb 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h
@@ -29,6 +29,8 @@ class CORE_EXPORT NGTextFragmentBuilder final : public NGBaseFragmentBuilder {
const NGInlineItemsData&,
NGInlineItemResult*,
LayoutUnit line_height);
+
+ // Set text for generated text, e.g. hyphen and ellipsis.
void SetText(LayoutObject*,
const String& text,
scoped_refptr<const ComputedStyle>,