diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/core/layout/ng/inline')
43 files changed, 1789 insertions, 702 deletions
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md b/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md index 87d62fbb0b3..715f1d0e84e 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md @@ -256,7 +256,7 @@ Computing baselines in LayoutNG goes the following process. Algorithms are responsible for checking [NGConstraintSpace]`::BaselineRequests()`, computing requested baselines, and -calling [NGFragmentBuilder]`::AddBaseline()` +calling [NGBoxFragmentBuilder]`::AddBaseline()` to add them to [NGPhysicalBoxFragment]. [NGBaselineRequest] consists of [NGBaselineAlgorithmType] and [FontBaseline]. @@ -314,9 +314,9 @@ In a bird's‐eye view, it consists of two parts: [NGBidiParagraph]: ng_bidi_paragraph.h [NGBlockNode]: ../ng_block_node.h [NGBoxFragment]: ../ng_box_fragment.h +[NGBoxFragmentBuilder]: ../ng_box_fragment_builder.h [NGConstraintSpace]: ../ng_constraint_space_builder.h [NGConstraintSpaceBuilder]: ../ng_constraint_space_builder.h -[NGFragmentBuilder]: ../ng_fragment_builder.h [NGInlineBoxState]: ng_inline_box_state.h [NGInlineItem]: ng_inline_item.h [NGInlineItemResult]: ng_inline_item_result.h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h index 5a456f2d8cf..d922dbf5684 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h @@ -6,18 +6,13 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_LAYOUT_NG_TEXT_H_ #include "third_party/blink/renderer/core/layout/layout_text.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_items.h" namespace blink { -class NGInlineItem; - // This overrides the default LayoutText to reference LayoutNGInlineItems // instead of InlineTextBoxes. // -// ***** INLINE ITEMS OWNERSHIP ***** -// NGInlineItems in items_ are not owned by LayoutText but are pointers into the -// LayoutNGBlockFlow's items_. Should not be accessed outside of layout. class CORE_EXPORT LayoutNGText : public LayoutText { public: LayoutNGText(Node* node, scoped_refptr<StringImpl> text) @@ -28,26 +23,6 @@ class CORE_EXPORT LayoutNGText : public LayoutText { } bool IsLayoutNGObject() const override { return true; } - bool HasValidLayout() const { return valid_ng_items_; } - const Vector<NGInlineItem*>& InlineItems() const { - DCHECK(valid_ng_items_); - return inline_items_; - } - - // Inline items depends on context. It needs to be invalidated not only when - // it was inserted/changed but also it was moved. - void InvalidateInlineItems() { valid_ng_items_ = false; } - - void ClearInlineItems() { - inline_items_.clear(); - valid_ng_items_ = false; - } - - void AddInlineItem(NGInlineItem* item) { - inline_items_.push_back(item); - valid_ng_items_ = true; - } - protected: void InsertedIntoTree() override { valid_ng_items_ = false; @@ -55,7 +30,10 @@ class CORE_EXPORT LayoutNGText : public LayoutText { } private: - Vector<NGInlineItem*> inline_items_; + const NGInlineItems* GetNGInlineItems() const final { return &inline_items_; } + NGInlineItems* GetNGInlineItems() final { return &inline_items_; } + + NGInlineItems inline_items_; }; DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGText, IsLayoutNGText()); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_fragment.h new file mode 100644 index 00000000000..130a7b4c8ea --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_fragment.h @@ -0,0 +1,40 @@ +// 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_LAYOUT_NG_TEXT_FRAGMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_LAYOUT_NG_TEXT_FRAGMENT_H_ + +#include "third_party/blink/renderer/core/layout/layout_text_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_items.h" + +namespace blink { + +// This overrides the default LayoutText to reference LayoutNGInlineItems +// instead of InlineTextBoxes. +// +class CORE_EXPORT LayoutNGTextFragment final : public LayoutTextFragment { + public: + LayoutNGTextFragment(Node* node, + StringImpl* text, + int start_offset, + int length) + : LayoutTextFragment(node, text, start_offset, length) {} + + bool IsLayoutNGObject() const final { return true; } + + private: + const NGInlineItems* GetNGInlineItems() const final { return &inline_items_; } + NGInlineItems* GetNGInlineItems() final { return &inline_items_; } + + void InsertedIntoTree() final { + valid_ng_items_ = false; + LayoutText::InsertedIntoTree(); + } + + NGInlineItems inline_items_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_LAYOUT_NG_TEXT_FRAGMENT_H_ 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 index 236022d3b6b..a416f652496 100644 --- 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 @@ -11,6 +11,7 @@ #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" +#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" namespace blink { @@ -146,9 +147,12 @@ void NGAbstractInlineTextBox::CharacterWidths(Vector<float>& widths) const { widths.resize(Len()); return; } - const ShapeResult& shape_result = *PhysicalTextFragment().TextShapeResult(); + // TODO(layout-dev): Add support for IndividualCharacterRanges to + // ShapeResultView to avoid the copy below. + auto shape_result = + PhysicalTextFragment().TextShapeResult()->CreateShapeResult(); Vector<CharacterRange> ranges; - shape_result.IndividualCharacterRanges(&ranges); + shape_result->IndividualCharacterRanges(&ranges); widths.ReserveCapacity(ranges.size()); widths.resize(0); for (const auto& range : ranges) 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 50e9985c623..f9059f05c5f 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 @@ -10,9 +10,26 @@ namespace blink { +const unsigned NGBaselineRequest::kTypeIdCount; +const LayoutUnit NGBaselineList::kEmptyOffset; + bool NGBaselineRequest::operator==(const NGBaselineRequest& other) const { - return algorithm_type == other.algorithm_type && - baseline_type == other.baseline_type; + return algorithm_type_ == other.algorithm_type_ && + baseline_type_ == other.baseline_type_; +} + +bool NGBaselineRequestList::operator==( + const NGBaselineRequestList& other) const { + return type_id_mask_ == other.type_id_mask_; +} + +void NGBaselineRequestList::push_back(const NGBaselineRequest& request) { + type_id_mask_ |= 1 << request.TypeId(); +} + +void NGBaselineRequestList::AppendVector( + const NGBaselineRequestList& requests) { + type_id_mask_ |= requests.type_id_mask_; } bool NGBaseline::ShouldPropagateBaselines(const NGLayoutInputNode node) { @@ -38,4 +55,35 @@ bool NGBaseline::ShouldPropagateBaselines(LayoutBox* layout_box) { return true; } +NGBaselineList::NGBaselineList() { + std::fill(std::begin(offsets_), std::end(offsets_), kEmptyOffset); +} + +bool NGBaselineList::IsEmpty() const { + for (LayoutUnit offset : offsets_) { + if (offset != kEmptyOffset) + return false; + } + return true; +} + +base::Optional<LayoutUnit> NGBaselineList::Offset( + const NGBaselineRequest request) const { + LayoutUnit offset = offsets_[request.TypeId()]; + if (offset != kEmptyOffset) + return offset; + return base::nullopt; +} + +void NGBaselineList::emplace_back(NGBaselineRequest request, + LayoutUnit offset) { + // Round LayoutUnit::Min() because we use it for an empty value. + DCHECK_EQ(kEmptyOffset, LayoutUnit::Min()) + << "Change the rounding if kEmptyOffset was changed"; + if (UNLIKELY(offset == LayoutUnit::Min())) + offset = LayoutUnit::NearlyMin(); + DCHECK_NE(offset, kEmptyOffset); + offsets_[request.TypeId()] = offset; +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h index cfcd9a218cb..edb7d9aeb12 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h @@ -5,8 +5,9 @@ #ifndef NGBaseline_h #define NGBaseline_h +#include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/platform/fonts/font_baseline.h" -#include "third_party/blink/renderer/platform/layout_unit.h" +#include "third_party/blink/renderer/platform/geometry/layout_unit.h" namespace blink { @@ -22,18 +23,103 @@ enum class NGBaselineAlgorithmType { // Baselines are products of layout. // To compute baseline, add requests to NGConstraintSpace and run Layout(). -struct NGBaselineRequest { - NGBaselineAlgorithmType algorithm_type; - FontBaseline baseline_type; +class CORE_EXPORT NGBaselineRequest { + public: + NGBaselineRequest(NGBaselineAlgorithmType algorithm_type, + FontBaseline baseline_type) + : algorithm_type_(static_cast<unsigned>(algorithm_type)), + baseline_type_(static_cast<unsigned>(baseline_type)) {} + + NGBaselineAlgorithmType AlgorithmType() const { + return static_cast<NGBaselineAlgorithmType>(algorithm_type_); + } + + FontBaseline BaselineType() const { + return static_cast<FontBaseline>(baseline_type_); + } bool operator==(const NGBaselineRequest& other) const; bool operator!=(const NGBaselineRequest& other) const { return !(*this == other); } + + private: + // TypeId is an integer that identifies all combinations of + // |NGBaselineRequest|. Visible only to |NGBaselineRequestList| and + // |NGBaselineList|. + static constexpr unsigned kTypeIdCount = 4; + unsigned TypeId() const { return algorithm_type_ | (baseline_type_ << 1); } + static NGBaselineRequest FromTypeId(unsigned type_id) { + DCHECK_LE(type_id, kTypeIdCount); + return NGBaselineRequest(static_cast<NGBaselineAlgorithmType>(type_id & 1), + static_cast<FontBaseline>((type_id >> 1) & 1)); + } + friend class NGBaselineList; + friend class NGBaselineRequestList; + friend class NGBaselineTest; + + unsigned algorithm_type_ : 1; // NGBaselineAlgorithmType + unsigned baseline_type_ : 1; // FontBaseline +}; + +// A list of |NGBaselineRequest| in a packed format, with similar interface as +// |Vector|. +class CORE_EXPORT NGBaselineRequestList { + public: + NGBaselineRequestList() = default; + + bool IsEmpty() const { return !type_id_mask_; } + + bool operator==(const NGBaselineRequestList& other) const; + + void push_back(const NGBaselineRequest& request); + void AppendVector(const NGBaselineRequestList& requests); + + class const_iterator { + public: + const_iterator() : type_id_(NGBaselineRequest::kTypeIdCount), mask_(0) {} + explicit const_iterator(unsigned mask) : type_id_(0), mask_(mask) { + if (!(mask_ & 1)) + ++(*this); + } + + const NGBaselineRequest operator*() const { + return NGBaselineRequest::FromTypeId(type_id_); + } + bool operator!=(const const_iterator& other) const { + return type_id_ != other.type_id_; + } + void operator++() { + while (type_id_ < NGBaselineRequest::kTypeIdCount) { + ++type_id_; + mask_ >>= 1; + if (mask_ & 1) + break; + } + } + + private: + unsigned type_id_; + unsigned mask_; + }; + + const_iterator begin() const { return const_iterator(type_id_mask_); } + const_iterator end() const { return const_iterator(); } + + private: + // Serialize/deserialize to a bit fields. + static constexpr unsigned kSerializedBits = NGBaselineRequest::kTypeIdCount; + unsigned Serialize() const { return type_id_mask_; } + explicit NGBaselineRequestList(unsigned serialized) + : type_id_mask_(serialized) {} + friend class NGConstraintSpace; + friend class NGConstraintSpaceBuilder; + + unsigned type_id_mask_ = 0; }; // Represents a computed baseline position. -struct NGBaseline { +struct CORE_EXPORT NGBaseline { NGBaselineRequest request; LayoutUnit offset; @@ -42,6 +128,59 @@ struct NGBaseline { static bool ShouldPropagateBaselines(LayoutBox*); }; +// A list of |NGBaseline| in a packed format, with similar interface as +// |Vector|. +class CORE_EXPORT NGBaselineList { + public: + NGBaselineList(); + + bool IsEmpty() const; + + base::Optional<LayoutUnit> Offset(const NGBaselineRequest request) const; + + void emplace_back(NGBaselineRequest request, LayoutUnit offset); + + class const_iterator { + public: + explicit const_iterator(unsigned type_id, const LayoutUnit* offset) + : type_id_(type_id), offset_(offset) { + DCHECK(offset); + if (*offset == kEmptyOffset) + ++(*this); + } + const_iterator() + : type_id_(NGBaselineRequest::kTypeIdCount), offset_(nullptr) {} + + const NGBaseline operator*() const { + return NGBaseline{NGBaselineRequest::FromTypeId(type_id_), *offset_}; + } + bool operator!=(const const_iterator& other) const { + return type_id_ != other.type_id_; + } + void operator++() { + while (type_id_ < NGBaselineRequest::kTypeIdCount) { + ++type_id_; + ++offset_; + if (type_id_ < NGBaselineRequest::kTypeIdCount && + *offset_ != kEmptyOffset) + break; + } + } + + private: + unsigned type_id_; + const LayoutUnit* offset_; + }; + + const_iterator begin() const { return const_iterator(0, offsets_); } + const_iterator end() const { return const_iterator(); } + + private: + static constexpr LayoutUnit kEmptyOffset = LayoutUnit::Min(); + + LayoutUnit offsets_[NGBaselineRequest::kTypeIdCount]; +}; + } // namespace blink #endif // NGBaseline_h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline_test.cc new file mode 100644 index 00000000000..4e311e71bdd --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline_test.cc @@ -0,0 +1,76 @@ +// 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_baseline.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; + +class NGBaselineTest : public testing::Test { + public: + static NGBaselineRequest RequestFromTypeId(unsigned type_id) { + return NGBaselineRequest::FromTypeId(type_id); + } + + static Vector<NGBaselineRequest> ToList(NGBaselineRequestList requests) { + Vector<NGBaselineRequest> list; + for (const NGBaselineRequest request : requests) + list.push_back(request); + return list; + } +}; + +struct NGBaselineRequestListTestData { + unsigned count; + unsigned type_ids[4]; +} baseline_request_list_test_data[] = { + {0, {}}, {1, {0}}, {1, {1}}, {1, {2}}, + {1, {3}}, {2, {0, 1}}, {2, {0, 2}}, {2, {1, 3}}, + {3, {0, 1, 2}}, {3, {0, 2, 3}}, {3, {1, 2, 3}}, {4, {0, 1, 2, 3}}, +}; + +class NGBaselineRequestListDataTest + : public NGBaselineTest, + public testing::WithParamInterface<NGBaselineRequestListTestData> {}; + +INSTANTIATE_TEST_CASE_P(NGBaselineTest, + NGBaselineRequestListDataTest, + testing::ValuesIn(baseline_request_list_test_data)); + +TEST_P(NGBaselineRequestListDataTest, Data) { + const auto& data = GetParam(); + NGBaselineRequestList requests; + Vector<NGBaselineRequest> expected; + for (unsigned i = 0; i < data.count; i++) { + NGBaselineRequest request = RequestFromTypeId(data.type_ids[i]); + requests.push_back(request); + expected.push_back(request); + } + + EXPECT_EQ(requests.IsEmpty(), !data.count); + + Vector<NGBaselineRequest> actual = ToList(requests); + EXPECT_THAT(actual, expected); +} + +TEST_F(NGBaselineTest, BaselineList) { + NGBaselineList list; + EXPECT_TRUE(list.IsEmpty()); + + NGBaselineRequest request(NGBaselineAlgorithmType::kFirstLine, + FontBaseline::kAlphabeticBaseline); + list.emplace_back(request, LayoutUnit(123)); + EXPECT_FALSE(list.IsEmpty()); + EXPECT_EQ(list.Offset(request), LayoutUnit(123)); + EXPECT_FALSE(list.Offset({NGBaselineAlgorithmType::kFirstLine, + FontBaseline::kIdeographicBaseline})); +} + +} // namespace blink 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 a139ca08482..d0267eb4142 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 @@ -269,12 +269,13 @@ NGCaretPosition ComputeNGCaretPosition(const LayoutBlockFlow& context, NGCaretPosition ComputeNGCaretPosition(const PositionWithAffinity& position) { AssertValidPositionForCaretPositionComputation(position); - const LayoutBlockFlow* context = + LayoutBlockFlow* context = NGInlineFormattingContextOf(position.GetPosition()); if (!context) return NGCaretPosition(); - const NGOffsetMapping* mapping = NGOffsetMapping::GetFor(context); + const NGOffsetMapping* mapping = + NGOffsetMapping::GetForContainingBlockFlow(context); DCHECK(mapping); const base::Optional<unsigned> maybe_offset = mapping->GetTextContentOffset(position.GetPosition()); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc index b0e7e25ba52..9d6fa0a16cf 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc @@ -141,7 +141,7 @@ LocalCaretRect ComputeLocalCaretRect(const NGCaretPosition& caret_position) { // See core/layout/README.md#coordinate-spaces for details. if (fragment.Style().IsFlippedBlocksWritingMode()) { const LayoutBlockFlow* container = - layout_object->EnclosingNGBlockFlow(); + layout_object->ContainingNGBlockFlow(); container->FlipForWritingMode(layout_rect); } 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 da1b76499c1..19a1adf2d90 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 @@ -9,9 +9,10 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h" -#include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" namespace blink { @@ -67,7 +68,7 @@ void NGInlineBoxState::EnsureTextMetrics(const ComputedStyle& style, ComputeTextMetrics(style, baseline_type); } -void NGInlineBoxState::AccumulateUsedFonts(const ShapeResult* shape_result, +void NGInlineBoxState::AccumulateUsedFonts(const ShapeResultView* shape_result, FontBaseline baseline_type) { HashSet<const SimpleFontData*> fallback_fonts; shape_result->FallbackFonts(&fallback_fonts); @@ -80,6 +81,15 @@ void NGInlineBoxState::AccumulateUsedFonts(const ShapeResult* shape_result, } } +LayoutUnit NGInlineBoxState::TextTop(FontBaseline baseline_type) const { + if (!text_metrics.IsEmpty()) + return text_top; + if (const SimpleFontData* font_data = style->GetFont().PrimaryFont()) + return -font_data->GetFontMetrics().FixedAscent(baseline_type); + NOTREACHED(); + return LayoutUnit(); +} + bool NGInlineBoxState::CanAddTextOfStyle( const ComputedStyle& text_style) const { if (text_style.VerticalAlign() != EVerticalAlign::kBaseline) @@ -158,10 +168,8 @@ NGInlineBoxState* NGInlineLayoutStateStack::OnOpenTag( NGInlineBoxState* box = OnOpenTag(*item.Style(), line_box); box->item = &item; - if (item.ShouldCreateBoxFragment()) { - box->SetNeedsBoxFragment( - ContainingLayoutObjectForAbsolutePositionObjects()); - } + if (item.ShouldCreateBoxFragment()) + box->SetNeedsBoxFragment(); // Compute box properties regardless of needs_box_fragment since close tag may // also set needs_box_fragment. @@ -208,6 +216,16 @@ void NGInlineLayoutStateStack::OnEndPlaceItems( box->has_end_edge = true; EndBoxState(box, line_box, baseline_type); } + + // Up to this point, the offset of inline boxes are stored in placeholder so + // that |ApplyBaselineShift()| can compute offset for both children and boxes. + // Copy the final offset to |box_data_list_|. + for (BoxData& box_data : box_data_list_) { + const NGLineBoxFragmentBuilder::Child& placeholder = + (*line_box)[box_data.fragment_end]; + DCHECK(!placeholder.HasFragment()); + box_data.offset = placeholder.offset; + } } void NGInlineLayoutStateStack::EndBoxState( @@ -232,13 +250,10 @@ void NGInlineLayoutStateStack::EndBoxState( parent_box.metrics.Unite(box->metrics); } -void NGInlineBoxState::SetNeedsBoxFragment( - const LayoutObject* inline_container) { +void NGInlineBoxState::SetNeedsBoxFragment() { DCHECK(item); DCHECK(!needs_box_fragment); needs_box_fragment = true; - DCHECK(!this->inline_container); - this->inline_container = inline_container; } // Crete a placeholder for a box fragment. @@ -250,30 +265,30 @@ void NGInlineLayoutStateStack::AddBoxFragmentPlaceholder( NGLineBoxFragmentBuilder::ChildList* line_box, FontBaseline baseline_type) { DCHECK(box->needs_box_fragment); - - // The inline box should have the height of the font metrics without the - // line-height property. Compute from style because |box->metrics| includes - // the line-height property. DCHECK(box->style); const ComputedStyle& style = *box->style; - NGLineHeightMetrics metrics(style, baseline_type); - // 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->borders.line_over + box->padding.line_over)); - NGLogicalSize size( - LayoutUnit(), - metrics.LineHeight() + box->borders.BlockSum() + box->padding.BlockSum()); + NGLogicalOffset offset; + NGLogicalSize size; + if (!is_empty_line_) { + // The inline box should have the height of the font metrics without the + // line-height property. Compute from style because |box->metrics| includes + // the line-height property. + NGLineHeightMetrics metrics(style, baseline_type); + + // Extend the block direction of the box by borders and paddings. Inline + // direction is already included into positions in NGLineBreaker. + offset.block_offset = + -metrics.ascent - (box->borders.line_over + box->padding.line_over); + size.block_size = metrics.LineHeight() + box->borders.BlockSum() + + box->padding.BlockSum(); + } unsigned fragment_end = line_box->size(); DCHECK(box->item); - box_data_list_.push_back( - BoxData{box->fragment_start, fragment_end, box->item, size}); - BoxData& box_data = box_data_list_.back(); + BoxData& box_data = box_data_list_.emplace_back( + box->fragment_start, fragment_end, box->item, size); box_data.padding = box->padding; - box_data.inline_container = box->inline_container; if (box->has_start_edge) { box_data.has_line_left_edge = true; box_data.margin_line_left = box->margin_inline_start; @@ -317,13 +332,17 @@ void NGInlineLayoutStateStack::AddBoxFragmentPlaceholder( box_data.size.inline_size = advance - box_data.margin_line_left - box_data.margin_line_right; line_box->AddChild(box_data.CreateBoxFragment(line_box), offset, advance, - 0); + /* bidi_level */ 0); box_data_list_.pop_back(); } } void NGInlineLayoutStateStack::PrepareForReorder( NGLineBoxFragmentBuilder::ChildList* line_box) { + // There's nothing to do if no boxes. + if (box_data_list_.IsEmpty()) + return; + // Set indexes of BoxData to the children of the line box. unsigned box_data_index = 0; for (const BoxData& box_data : box_data_list_) { @@ -341,43 +360,29 @@ void NGInlineLayoutStateStack::PrepareForReorder( const NGLineBoxFragmentBuilder::Child& placeholder = (*line_box)[box_data.fragment_end]; DCHECK(!placeholder.HasFragment()); - box_data.offset = placeholder.offset; - box_data.box_data_index = placeholder.box_data_index; + box_data.parent_box_data_index = placeholder.box_data_index; } } void NGInlineLayoutStateStack::UpdateAfterReorder( NGLineBoxFragmentBuilder::ChildList* line_box) { + // There's nothing to do if no boxes. + if (box_data_list_.IsEmpty()) + return; + // Compute start/end of boxes from the children of the line box. + // Clear start/end first. 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 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]; - if (!box_data.fragment_end) - box_data.fragment_start = i; - box_data.fragment_end = i + 1; - } - } - // Extend start/end of boxes when they are nested. - 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) { - parent_box_data.fragment_start = box_data.fragment_start; - parent_box_data.fragment_end = box_data.fragment_end; - } else { - parent_box_data.fragment_start = - std::min(box_data.fragment_start, parent_box_data.fragment_start); - parent_box_data.fragment_end = - std::max(box_data.fragment_end, parent_box_data.fragment_end); - } - } - } + // Scan children and update start/end from their box_data_index. + unsigned box_count = box_data_list_.size(); + for (unsigned index = 0; index < line_box->size();) + index = UpdateBoxDataFragmentRange(line_box, index); + + // If any inline fragmentation due to BiDi reorder, adjust box edges. + if (box_count != box_data_list_.size()) + UpdateFragmentedBoxDataEdges(); #if DCHECK_IS_ON() // Check all BoxData have ranges. @@ -385,15 +390,102 @@ void NGInlineLayoutStateStack::UpdateAfterReorder( DCHECK_NE(box_data.fragment_end, 0u); DCHECK_GT(box_data.fragment_end, box_data.fragment_start); } + // Check all |box_data_index| were migrated to BoxData. + for (const NGLineBoxFragmentBuilder::Child& child : *line_box) { + DCHECK_EQ(child.box_data_index, 0u); + } #endif } +unsigned NGInlineLayoutStateStack::UpdateBoxDataFragmentRange( + NGLineBoxFragmentBuilder::ChildList* line_box, + unsigned index) { + // Find the first line box item that should create a box fragment. + for (; index < line_box->size(); index++) { + NGLineBoxFragmentBuilder::Child* start = &(*line_box)[index]; + if (start->IsPlaceholder()) + continue; + const unsigned box_data_index = start->box_data_index; + if (!box_data_index) + continue; + + // As |box_data_index| is converted to start/end of BoxData, update + // |box_data_index| to the parent box, or to 0 if no parent boxes. + // This allows including this box to the nested parent box. + BoxData* box_data = &box_data_list_[box_data_index - 1]; + start->box_data_index = box_data->parent_box_data_index; + + // Find the end line box item. + const unsigned start_index = index; + for (index++; index < line_box->size(); index++) { + NGLineBoxFragmentBuilder::Child* end = &(*line_box)[index]; + if (end->IsPlaceholder()) + continue; + + // If we found another box that maybe included in this box, update it + // first. Updating will change |end->box_data_index| so that we can + // determine if it should be included into this box or not. + // It also changes other BoxData, but not the one we're dealing with here + // because the update is limited only when its |box_data_index| is lower. + while (end->box_data_index && end->box_data_index < box_data_index) { + UpdateBoxDataFragmentRange(line_box, index); + // Re-compute |box_data| in case |box_data_list_| was reallocated when + // |UpdateBoxDataFragmentRange| added new fragments. + box_data = &box_data_list_[box_data_index - 1]; + } + + if (box_data_index != end->box_data_index) + break; + end->box_data_index = box_data->parent_box_data_index; + } + + // If this is the first range for this BoxData, set it. + if (!box_data->fragment_end) { + box_data->fragment_start = start_index; + box_data->fragment_end = index; + } else { + // This box is fragmented by BiDi reordering. Add a new BoxData for the + // fragmented range. + box_data->fragmented_box_data_index = box_data_list_.size(); + box_data_list_.emplace_back(*box_data, start_index, index); + } + return box_data->parent_box_data_index ? start_index : index; + } + return index; +} + +void NGInlineLayoutStateStack::UpdateFragmentedBoxDataEdges() { + for (BoxData& box_data : box_data_list_) { + if (box_data.fragmented_box_data_index) + box_data.UpdateFragmentEdges(box_data_list_); + } +} + +void NGInlineLayoutStateStack::BoxData::UpdateFragmentEdges( + Vector<BoxData, 4>& list) { + DCHECK(fragmented_box_data_index); + + // If this box has the right edge, move it to the last fragment. + if (has_line_right_edge) { + BoxData& last = list[fragmented_box_data_index]; + last.has_line_right_edge = true; + last.margin_line_right = margin_line_right; + last.margin_border_padding_line_right = margin_border_padding_line_right; + last.padding.inline_end = padding.inline_end; + + has_line_right_edge = false; + margin_line_right = margin_border_padding_line_right = padding.inline_end = + LayoutUnit(); + } +} + LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions( NGLineBoxFragmentBuilder::ChildList* line_box) { // 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 (NGLineBoxFragmentBuilder::Child& child : *line_box) { + child.margin_line_left = child.offset.inline_offset; 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. @@ -439,7 +531,8 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions( // 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; + LayoutUnit line_left_offset = + start_child.offset.inline_offset - start_child.margin_line_left; 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; @@ -447,8 +540,9 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions( 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; + LayoutUnit line_right_offset = last_child.offset.inline_offset - + last_child.margin_line_left + + 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; @@ -494,8 +588,8 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( const ComputedStyle& style = *item->Style(); // Because children are already in the visual order, use LTR for the // fragment builder so that it should not transform the coordinates for RTL. - NGFragmentBuilder box(item->GetLayoutObject(), &style, style.GetWritingMode(), - TextDirection::kLtr); + NGBoxFragmentBuilder box(item->GetLayoutObject(), &style, + style.GetWritingMode(), TextDirection::kLtr); box.SetBoxType(NGPhysicalFragment::kInlineBox); box.SetStyleVariant(item->StyleVariant()); @@ -519,7 +613,8 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( // NGInlineLayoutAlgorithm can handle them later. DCHECK(!child.HasInFlowFragment()); } - box.MoveOutOfFlowDescendantCandidatesToDescendants(inline_container); + + box.MoveOutOfFlowDescendantCandidatesToDescendants(); return box.ToInlineBoxFragment(); } @@ -533,15 +628,14 @@ NGInlineLayoutStateStack::ApplyBaselineShift( // |pending_descendants|. LayoutUnit baseline_shift; if (!box->pending_descendants.IsEmpty()) { - NGLineHeightMetrics max = box->MetricsForTopAndBottomAlign(); + NGLineHeightMetrics max = MetricsForTopAndBottomAlign(*box, *line_box); for (NGPendingPositions& child : box->pending_descendants) { // In quirks mode, metrics is empty if no content. if (child.metrics.IsEmpty()) child.metrics = NGLineHeightMetrics::Zero(); switch (child.vertical_align) { case EVerticalAlign::kTextTop: - DCHECK(!box->text_metrics.IsEmpty()); - baseline_shift = child.metrics.ascent + box->text_top; + baseline_shift = child.metrics.ascent + box->TextTop(baseline_type); break; case EVerticalAlign::kTop: baseline_shift = child.metrics.ascent - max.ascent; @@ -646,30 +740,42 @@ NGInlineLayoutStateStack::ApplyBaselineShift( return kPositionNotPending; } -NGLineHeightMetrics NGInlineBoxState::MetricsForTopAndBottomAlign() const { +NGLineHeightMetrics NGInlineLayoutStateStack::MetricsForTopAndBottomAlign( + const NGInlineBoxState& box, + const NGLineBoxFragmentBuilder::ChildList& line_box) const { + DCHECK(!box.pending_descendants.IsEmpty()); + // |metrics| is the bounds of "aligned subtree", that is, bounds of // descendants that are not 'vertical-align: top' nor 'bottom'. // https://drafts.csswg.org/css2/visudet.html#propdef-vertical-align - NGLineHeightMetrics box = metrics; + NGLineHeightMetrics metrics = box.metrics; + + // BoxData contains inline boxes to be created later. Take them into account. + for (const BoxData& box_data : box_data_list_) { + LayoutUnit box_ascent = + -line_box[box_data.fragment_end].offset.block_offset; + metrics.Unite( + NGLineHeightMetrics(box_ascent, box_data.size.block_size - box_ascent)); + } // In quirks mode, metrics is empty if no content. - if (box.IsEmpty()) - box = NGLineHeightMetrics::Zero(); + if (metrics.IsEmpty()) + metrics = NGLineHeightMetrics::Zero(); // If the height of a box that has 'vertical-align: top' or 'bottom' exceeds // the height of the "aligned subtree", align the edge to the "aligned // subtree" and extend the other edge. - NGLineHeightMetrics max = box; - for (const NGPendingPositions& child : pending_descendants) { + NGLineHeightMetrics max = metrics; + for (const NGPendingPositions& child : box.pending_descendants) { if ((child.vertical_align == EVerticalAlign::kTop || child.vertical_align == EVerticalAlign::kBottom) && child.metrics.LineHeight() > max.LineHeight()) { if (child.vertical_align == EVerticalAlign::kTop) { - max = NGLineHeightMetrics(box.ascent, - child.metrics.LineHeight() - box.ascent); + max = NGLineHeightMetrics(metrics.ascent, + child.metrics.LineHeight() - metrics.ascent); } else if (child.vertical_align == EVerticalAlign::kBottom) { - max = NGLineHeightMetrics(child.metrics.LineHeight() - box.descent, - box.descent); + max = NGLineHeightMetrics(child.metrics.LineHeight() - metrics.descent, + metrics.descent); } } } @@ -693,7 +799,6 @@ void NGInlineBoxState::CheckSame(const NGInlineBoxState& other) const { DCHECK_EQ(fragment_start, other.fragment_start); DCHECK_EQ(item, other.item); DCHECK_EQ(style, other.style); - DCHECK_EQ(inline_container, other.inline_container); DCHECK_EQ(metrics, other.metrics); DCHECK_EQ(text_metrics, other.text_metrics); 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 b49228cec95..80d9eb33bf0 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 @@ -10,7 +10,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h" #include "third_party/blink/renderer/core/style/computed_style_constants.h" #include "third_party/blink/renderer/platform/fonts/font_baseline.h" -#include "third_party/blink/renderer/platform/layout_unit.h" +#include "third_party/blink/renderer/platform/geometry/layout_unit.h" #include "third_party/blink/renderer/platform/wtf/vector.h" namespace blink { @@ -18,7 +18,7 @@ namespace blink { class LayoutObject; class NGInlineItem; struct NGInlineItemResult; -class ShapeResult; +class ShapeResultView; // Fragments that require the layout position/size of ancestor are packed in // this struct. @@ -40,7 +40,6 @@ struct NGInlineBoxState { unsigned fragment_start = 0; const NGInlineItem* item = nullptr; const ComputedStyle* style = nullptr; - const LayoutObject* inline_container = nullptr; // The united metrics for the current box. This includes all objects in this // box, including descendants, and adjusted by placement properties such as @@ -85,20 +84,19 @@ struct NGInlineBoxState { void EnsureTextMetrics(const ComputedStyle&, FontBaseline); void ResetTextMetrics(); - void AccumulateUsedFonts(const ShapeResult*, FontBaseline); + void AccumulateUsedFonts(const ShapeResultView*, FontBaseline); + + // 'text-top' offset for 'vertical-align'. + LayoutUnit TextTop(FontBaseline baseline_type) const; // Create a box fragment for this box. - void SetNeedsBoxFragment(const LayoutObject* inline_container); + void SetNeedsBoxFragment(); // 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. bool CanAddTextOfStyle(const ComputedStyle&) const; - // Compute the metrics for when 'vertical-align' is 'top' and 'bottom' from - // |pending_descendants|. - NGLineHeightMetrics MetricsForTopAndBottomAlign() const; - #if DCHECK_IS_ON() void CheckSame(const NGInlineBoxState&) const; #endif @@ -115,6 +113,8 @@ class CORE_EXPORT NGInlineLayoutStateStack { // The box state for the line box. NGInlineBoxState& LineBoxState() { return stack_.front(); } + void SetIsEmptyLine(bool is_empty_line) { is_empty_line_ = is_empty_line; } + // Initialize the box state stack for a new line. // @return The initial box state for the line. NGInlineBoxState* OnBeginPlaceItems(const ComputedStyle*, FontBaseline, bool); @@ -149,6 +149,18 @@ class CORE_EXPORT NGInlineLayoutStateStack { // reordering. void UpdateAfterReorder(NGLineBoxFragmentBuilder::ChildList*); + // Update start/end of the first BoxData found at |index|. + // + // If inline fragmentation is found, a new BoxData is added. + // + // Returns the index to process next. It should be given to the next call to + // this function. + unsigned UpdateBoxDataFragmentRange(NGLineBoxFragmentBuilder::ChildList*, + unsigned index); + + // Update edges of inline fragmented boxes. + void UpdateFragmentedBoxDataEdges(); + // Compute inline positions of fragments and boxes. LayoutUnit ComputeInlinePositions(NGLineBoxFragmentBuilder::ChildList*); @@ -183,15 +195,35 @@ class CORE_EXPORT NGInlineLayoutStateStack { NGLineBoxFragmentBuilder::ChildList*, FontBaseline); + // Compute the metrics for when 'vertical-align' is 'top' and 'bottom' from + // |pending_descendants|. + NGLineHeightMetrics MetricsForTopAndBottomAlign( + const NGInlineBoxState&, + const NGLineBoxFragmentBuilder::ChildList&) const; + // Data for a box fragment. See AddBoxFragmentPlaceholder(). // This is a transient object only while building a line box. struct BoxData { + BoxData(unsigned start, + unsigned end, + const NGInlineItem* item, + NGLogicalSize size) + : fragment_start(start), fragment_end(end), item(item), size(size) {} + + BoxData(const BoxData& other, unsigned start, unsigned end) + : fragment_start(start), + fragment_end(end), + item(other.item), + size(other.size), + offset(other.offset) {} + + // The range of child fragments this box contains. unsigned fragment_start; unsigned fragment_end; + const NGInlineItem* item; NGLogicalSize size; - const LayoutObject* inline_container = nullptr; bool has_line_left_edge = false; bool has_line_right_edge = false; NGLineBoxStrut padding; @@ -202,7 +234,10 @@ class CORE_EXPORT NGInlineLayoutStateStack { LayoutUnit margin_border_padding_line_right; NGLogicalOffset offset; - unsigned box_data_index = 0; + unsigned parent_box_data_index = 0; + unsigned fragmented_box_data_index = 0; + + void UpdateFragmentEdges(Vector<BoxData, 4>& list); scoped_refptr<NGLayoutResult> CreateBoxFragment( NGLineBoxFragmentBuilder::ChildList*); @@ -210,6 +245,8 @@ class CORE_EXPORT NGInlineLayoutStateStack { Vector<NGInlineBoxState, 4> stack_; Vector<BoxData, 4> box_data_list_; + + bool is_empty_line_ = false; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h index cbacb3243a1..ff301ee8364 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h @@ -12,7 +12,7 @@ namespace blink { // Represents a break token for an inline node. -class CORE_EXPORT NGInlineBreakToken : public NGBreakToken { +class CORE_EXPORT NGInlineBreakToken final : public NGBreakToken { public: enum NGInlineBreakTokenFlags { kDefault = 0, 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 765ddf3e5cd..00946fc018e 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 @@ -6,7 +6,6 @@ #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/ng/ng_outline_utils.h" #include "third_party/blink/renderer/core/style/computed_style.h" #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h" @@ -23,32 +22,37 @@ const char* kNGInlineItemTypeStrings[] = { // 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. https://drafts.csswg.org/css2/visuren.html -bool IsInlineBoxEmpty(const ComputedStyle& style, - const LayoutObject& layout_object) { - if (style.BorderStart().NonZero() || !style.PaddingStart().IsZero() || - style.BorderEnd().NonZero() || !style.PaddingEnd().IsZero()) +bool IsInlineBoxStartEmpty(const ComputedStyle& style, + const LayoutObject& layout_object) { + if (style.BorderStartWidth() || !style.PaddingStart().IsZero()) return false; // Non-zero margin can prevent "empty" only in non-quirks mode. // https://quirks.spec.whatwg.org/#the-line-height-calculation-quirk - if ((!style.MarginStart().IsZero() || !style.MarginEnd().IsZero()) && + if (!style.MarginStart().IsZero() && !layout_object.GetDocument().InLineHeightQuirksMode()) return false; 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; - } +// Determines if the end of a box is "empty" as defined above. +// +// Keeping the "empty" state for start and end separately is important when they +// belong to different lines, as non-empty item can force the line it belongs to +// as non-empty. +bool IsInlineBoxEndEmpty(const ComputedStyle& style, + const LayoutObject& layout_object) { + if (style.BorderEndWidth() || !style.PaddingEnd().IsZero()) + return false; + + // Non-zero margin can prevent "empty" only in non-quirks mode. + // https://quirks.spec.whatwg.org/#the-line-height-calculation-quirk + if (!style.MarginEnd().IsZero() && + !layout_object.GetDocument().InLineHeightQuirksMode()) + return false; + + return true; } } // namespace @@ -69,7 +73,6 @@ NGInlineItem::NGInlineItem(NGInlineItemType type, 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), is_end_collapsible_newline_(false), @@ -95,7 +98,6 @@ NGInlineItem::NGInlineItem(const NGInlineItem& other, 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_), is_end_collapsible_newline_(other.is_end_collapsible_newline_), @@ -106,9 +108,20 @@ NGInlineItem::NGInlineItem(const NGInlineItem& other, NGInlineItem::~NGInlineItem() = default; +bool NGInlineItem::ShouldCreateBoxFragment() const { + if (Type() == kOpenTag || Type() == kCloseTag) + return ToLayoutInline(layout_object_)->ShouldCreateBoxFragment(); + DCHECK_EQ(Type(), kAtomicInline); + return false; +} + +void NGInlineItem::SetShouldCreateBoxFragment() { + DCHECK(Type() == kOpenTag || Type() == kCloseTag); + ToLayoutInline(layout_object_)->SetShouldCreateBoxFragment(); +} + void NGInlineItem::ComputeBoxProperties() { DCHECK(!is_empty_item_); - DCHECK(!should_create_box_fragment_); if (type_ == NGInlineItem::kText || type_ == NGInlineItem::kAtomicInline || type_ == NGInlineItem::kControl) @@ -116,24 +129,13 @@ void NGInlineItem::ComputeBoxProperties() { if (type_ == NGInlineItem::kOpenTag) { DCHECK(style_ && layout_object_ && layout_object_->IsLayoutInline()); - if (style_->HasBoxDecorationBackground() || style_->HasPadding() || - style_->HasMargin()) { - is_empty_item_ = IsInlineBoxEmpty(*style_, *layout_object_); - should_create_box_fragment_ = true; - } else { - is_empty_item_ = true; - should_create_box_fragment_ = - ToLayoutBoxModelObject(layout_object_)->HasSelfPaintingLayer() || - style_->CanContainAbsolutePositionObjects() || - style_->CanContainFixedPositionObjects(false) || - NGOutlineUtils::HasPaintedOutline(*style_, - layout_object_->GetNode()) || - ToLayoutBoxModelObject(layout_object_) - ->ShouldApplyPaintContainment() || - ToLayoutBoxModelObject(layout_object_) - ->ShouldApplyLayoutContainment() || - CanBeHitTestTargetPseudoNodeStyle(*style_); - } + is_empty_item_ = IsInlineBoxStartEmpty(*style_, *layout_object_); + return; + } + + if (type_ == NGInlineItem::kCloseTag) { + DCHECK(style_ && layout_object_ && layout_object_->IsLayoutInline()); + is_empty_item_ = IsInlineBoxEndEmpty(*style_, *layout_object_); return; } 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 fff083fed85..8becfc61e55 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 @@ -91,8 +91,8 @@ class CORE_EXPORT NGInlineItem { // If this item should create a box fragment. Box fragments can be omitted for // optimization if this is false. - bool ShouldCreateBoxFragment() const { return should_create_box_fragment_; } - void SetShouldCreateBoxFragment() { should_create_box_fragment_ = true; } + bool ShouldCreateBoxFragment() const; + void SetShouldCreateBoxFragment(); unsigned StartOffset() const { return start_offset_; } unsigned EndOffset() const { return end_offset_; } @@ -196,7 +196,6 @@ class CORE_EXPORT NGInlineItem { 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 is_end_collapsible_newline_ : 1; 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 d81c185d04e..960648adae9 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 @@ -6,6 +6,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" +#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" namespace blink { @@ -25,30 +26,10 @@ NGInlineItemResult::NGInlineItemResult(const NGInlineItem* item, void NGLineInfo::SetLineStyle(const NGInlineNode& node, const NGInlineItemsData& items_data, - const NGConstraintSpace& constraint_space, - bool is_first_line, - bool use_first_line_style, - bool is_after_forced_break) { + bool use_first_line_style) { use_first_line_style_ = use_first_line_style; items_data_ = &items_data; 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 - // containing block. - // https://drafts.csswg.org/css-text-3/#valdef-text-indent-percentage - // In our constraint space tree, parent constraint space is of its - // containing block. - // TODO(kojii): ComputeMinMaxSize does not know parent constraint - // space that we cannot compute percent for text-indent. - const Length& length = line_style_->TextIndent(); - LayoutUnit maximum_value; - if (length.IsPercentOrCalc()) - maximum_value = constraint_space.ParentPercentageResolutionInlineSize(); - text_indent_ = MinimumValueForLength(length, maximum_value); - } else { - text_indent_ = LayoutUnit(); - } } #if DCHECK_IS_ON() @@ -60,8 +41,8 @@ void NGInlineItemResult::CheckConsistency(bool during_line_break) const { 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()); + DCHECK_EQ(start_offset, shape_result->StartIndex()); + DCHECK_EQ(end_offset, shape_result->EndIndex()); } } #endif 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 da38185ccf8..e10711c4ea3 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 @@ -10,12 +10,11 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_end_effect.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h" -#include "third_party/blink/renderer/platform/layout_unit.h" +#include "third_party/blink/renderer/platform/geometry/layout_unit.h" #include "third_party/blink/renderer/platform/wtf/allocator.h" namespace blink { -class NGConstraintSpace; class NGInlineItem; class NGInlineNode; @@ -45,7 +44,7 @@ struct CORE_EXPORT NGInlineItemResult { // ShapeResult for text items. Maybe different from NGInlineItem if re-shape // is needed in the line breaker. - scoped_refptr<const ShapeResult> shape_result; + scoped_refptr<const ShapeResultView> shape_result; // NGLayoutResult for atomic inline items. scoped_refptr<NGLayoutResult> layout_result; @@ -132,10 +131,7 @@ class CORE_EXPORT NGLineInfo { } void SetLineStyle(const NGInlineNode&, const NGInlineItemsData&, - const NGConstraintSpace&, - bool is_first_formatted_line, - bool use_first_line_style, - bool is_after_forced_break); + bool use_first_line_style); // Use ::first-line style if true. // https://drafts.csswg.org/css-pseudo/#selectordef-first-line @@ -159,6 +155,7 @@ class CORE_EXPORT NGLineInfo { NGInlineItemResults* MutableResults() { return &results_; } const NGInlineItemResults& Results() const { return results_; } + void SetTextIndent(LayoutUnit indent) { text_indent_ = indent; } LayoutUnit TextIndent() const { return text_indent_; } NGBfcOffset BfcOffset() const { return bfc_offset_; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items.h new file mode 100644 index 00000000000..6990f6d2a23 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items.h @@ -0,0 +1,37 @@ +// 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_INLINE_ITEMS_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_ITEMS_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class NGInlineItem; + +// A collection of |NGInlineItem| associated to |LayoutNGText|. +// +// ***** INLINE ITEMS OWNERSHIP ***** +// NGInlineItems in items_ are not owned by LayoutText but are pointers into the +// LayoutNGBlockFlow's items_. Should not be accessed outside of layout. +class NGInlineItems final { + public: + NGInlineItems() = default; + + void Add(NGInlineItem* item) { items_.push_back(item); } + void Clear() { items_.clear(); } + const Vector<NGInlineItem*>& Items() const { return items_; } + + private: + Vector<NGInlineItem*> items_; + + DISALLOW_COPY_AND_ASSIGN(NGInlineItems); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_ITEMS_H_ 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 1757b0fd6e1..2fe5b2fd4a1 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 @@ -4,14 +4,23 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h" +#include <type_traits> + #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" #include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.h" #include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" namespace blink { +// Returns true if items builder is used for other than offset mapping. +template <typename OffsetMappingBuilder> +bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::NeedsBoxInfo() { + return !std::is_same<NGOffsetMappingBuilder, OffsetMappingBuilder>::value; +} + template <typename OffsetMappingBuilder> NGInlineItemsBuilderTemplate< OffsetMappingBuilder>::~NGInlineItemsBuilderTemplate() { @@ -21,11 +30,6 @@ NGInlineItemsBuilderTemplate< template <typename OffsetMappingBuilder> String NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::ToString() { - // Segment Break Transformation Rules[1] defines to keep trailing new lines, - // but it will be removed in Phase II[2]. We prefer not to add trailing new - // lines and collapsible spaces in Phase I. - RemoveTrailingCollapsibleSpaceIfExists(); - return text_.ToString(); } @@ -247,8 +251,8 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>:: template <typename OffsetMappingBuilder> bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::Append( const String& original_string, - LayoutNGText* layout_text, - const Vector<NGInlineItem*>& items) { + LayoutText* layout_text) { + const Vector<NGInlineItem*>& items = layout_text->InlineItems(); // Don't reuse existing items if they might be affected by whitespace // collapsing. // TODO(layout-dev): This could likely be optimized further. @@ -335,8 +339,8 @@ bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::Append( DCHECK_EQ(start, adjusted_item.StartOffset()); DCHECK_EQ(end, adjusted_item.EndOffset()); if (adjusted_item.TextShapeResult()) { - DCHECK_EQ(start, adjusted_item.TextShapeResult()->StartIndexForResult()); - DCHECK_EQ(end, adjusted_item.TextShapeResult()->EndIndexForResult()); + DCHECK_EQ(start, adjusted_item.TextShapeResult()->StartIndex()); + DCHECK_EQ(end, adjusted_item.TextShapeResult()->EndIndex()); } DCHECK_EQ(item->IsEmptyItem(), adjusted_item.IsEmptyItem()); #endif @@ -348,10 +352,8 @@ bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::Append( } template <> -bool NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::Append( - const String&, - LayoutNGText*, - const Vector<NGInlineItem*>&) { +bool NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::Append(const String&, + LayoutText*) { NOTREACHED(); return false; } @@ -600,7 +602,7 @@ void NGInlineItemsBuilderTemplate< continue; } - size_t end = string.Find(IsControlItemCharacter, start + 1); + wtf_size_t end = string.Find(IsControlItemCharacter, start + 1); if (end == kNotFound) end = string.length(); AppendTextItem(string, start, end, style, layout_object); @@ -620,7 +622,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendPreserveNewline( continue; } - size_t end = string.find(kNewlineCharacter, start + 1); + wtf_size_t end = string.find(kNewlineCharacter, start + 1); if (end == kNotFound) end = string.length(); DCHECK_GE(end, start); @@ -777,8 +779,13 @@ void NGInlineItemsBuilderTemplate< // Remove the item if the item has only one space that we're removing. if (item->Length() == 1) { DCHECK_EQ(item->StartOffset(), space_offset); - unsigned index = std::distance(items_->begin(), item); + wtf_size_t index = + static_cast<wtf_size_t>(std::distance(items_->begin(), item)); items_->EraseAt(index); + for (BoxInfo& box : boxes_) { + if (box.item_index >= index) + --box.item_index; + } if (index == items_->size()) return; // Re-compute |item| because |EraseAt| may have reallocated the buffer. @@ -937,6 +944,9 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::EnterInline( AppendOpaque(NGInlineItem::kOpenTag, style, node); + if (!NeedsBoxInfo()) + return; + // Set |ShouldCreateBoxFragment| of the parent box if needed. BoxInfo* current_box = &boxes_.emplace_back(items_->size() - 1, items_->back()); @@ -952,6 +962,11 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::EnterInline( template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::ExitBlock() { Exit(nullptr); + + // Segment Break Transformation Rules[1] defines to keep trailing new lines, + // but it will be removed in Phase II[2]. We prefer not to add trailing new + // lines and collapsible spaces in Phase I. + RemoveTrailingCollapsibleSpaceIfExists(); } template <typename OffsetMappingBuilder> @@ -961,7 +976,8 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::ExitInline( AppendOpaque(NGInlineItem::kCloseTag, node->Style(), node); - boxes_.pop_back(); + if (NeedsBoxInfo()) + boxes_.pop_back(); Exit(node); 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 b72fb3970ff..1a09cfdb092 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 @@ -18,7 +18,6 @@ namespace blink { class ComputedStyle; -class LayoutNGText; class LayoutObject; class LayoutText; @@ -59,7 +58,7 @@ class NGInlineItemsBuilderTemplate { // Returns whether the existing items could be reused. // NOTE: The state of the builder remains unchanged if the append operation // fails (i.e. if it returns false). - bool Append(const String&, LayoutNGText*, const Vector<NGInlineItem*>&); + bool Append(const String&, LayoutText*); // Append a string. // When appending, spaces are collapsed according to CSS Text, The white space @@ -112,6 +111,8 @@ class NGInlineItemsBuilderTemplate { void SetIsSymbolMarker(bool b); private: + static bool NeedsBoxInfo(); + Vector<NGInlineItem>* items_; StringBuilder text_; @@ -185,8 +186,7 @@ class NGInlineItemsBuilderTemplate { template <> CORE_EXPORT bool NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::Append( const String&, - LayoutNGText*, - const Vector<NGInlineItem*>&); + LayoutText*); extern template class CORE_EXTERN_TEMPLATE_EXPORT NGInlineItemsBuilderTemplate<EmptyOffsetMappingBuilder>; 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 12dbf885f0e..bdbc6558e00 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 @@ -57,6 +57,7 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { } builder.Append(input.text, input.layout_text->Style(), input.layout_text); } + builder.ExitBlock(); text_ = builder.ToString(); ValidateItems(); CheckReuseItemsProducesSameResult(inputs); @@ -97,23 +98,21 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { for (Input& input : inputs) { // Collect items for this LayoutObject. DCHECK(input.layout_text); - Vector<NGInlineItem*> previous_items; for (auto& item : items_) { if (item.GetLayoutObject() == input.layout_text) - previous_items.push_back(&item); + input.layout_text->AddInlineItem(&item); } // Try to re-use previous items, or Append if it was not re-usable. - bool reused = - !previous_items.IsEmpty() && - reuse_builder.Append(text_, ToLayoutNGText(input.layout_text), - previous_items); + bool reused = input.layout_text->HasValidInlineItems() && + reuse_builder.Append(text_, input.layout_text); if (!reused) { reuse_builder.Append(input.text, input.layout_text->Style(), input.layout_text); } } + reuse_builder.ExitBlock(); String reuse_text = reuse_builder.ToString(); EXPECT_EQ(text_, reuse_text); } @@ -406,6 +405,7 @@ static std::unique_ptr<LayoutInline> CreateLayoutInline( initialize_style(style.get()); std::unique_ptr<LayoutInline> node = std::make_unique<LayoutInline>(nullptr); node->SetStyleInternal(std::move(style)); + node->SetIsInLayoutNGInlineFormattingContext(true); return node; } 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 94888b48188..87358fff774 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 @@ -6,6 +6,7 @@ #include <memory> +#include "third_party/blink/renderer/core/layout/logical_values.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.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_box_state.h" @@ -31,6 +32,7 @@ #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/shape_result_spacing.h" +#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" namespace blink { namespace { @@ -129,6 +131,17 @@ NGInlineBoxState* NGInlineLayoutAlgorithm::HandleOpenTag( return box; } +NGInlineBoxState* NGInlineLayoutAlgorithm::HandleCloseTag( + const NGInlineItem& item, + const NGInlineItemResult& item_result, + NGInlineBoxState* box) { + if (UNLIKELY(quirks_mode_ && !item.IsEmptyItem())) + box->EnsureTextMetrics(*item.Style(), baseline_type_); + box = box_states_->OnCloseTag(&line_box_, box, baseline_type_, + item.HasEndEdge()); + return box; +} + // Prepare NGInlineLayoutStateStack for a new line. void NGInlineLayoutAlgorithm::PrepareBoxStates( const NGLineInfo& line_info, @@ -206,7 +219,7 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info, NGExclusionSpace* exclusion_space) { // Needs MutableResults to move ShapeResult out of the NGLineInfo. NGInlineItemResults* line_items = line_info->MutableResults(); - line_box_.clear(); + line_box_.resize(0); // Apply justification before placing items, because it affects size/position // of items, which are needed to compute inline static positions. @@ -226,6 +239,7 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info, // Compute heights of all inline items by placing the dominant baseline at 0. // The baseline is adjusted after the height of the line box is computed. + box_states_->SetIsEmptyLine(line_info->IsEmptyLine()); NGInlineBoxState* box = box_states_->OnBeginPlaceItems(&line_style, baseline_type_, quirks_mode_); #if DCHECK_IS_ON() @@ -248,7 +262,7 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info, item.GetLayoutObject()->IsLayoutNGListItem()); DCHECK(item_result.shape_result); - if (quirks_mode_) + if (UNLIKELY(quirks_mode_)) box->EnsureTextMetrics(*item.Style(), baseline_type_); // Take all used fonts into account if 'line-height: normal'. @@ -273,12 +287,9 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info, } else if (item.Type() == NGInlineItem::kOpenTag) { box = HandleOpenTag(item, item_result, box_states_); } else if (item.Type() == NGInlineItem::kCloseTag) { - if (quirks_mode_ && box->needs_box_fragment) - box->EnsureTextMetrics(*item.Style(), baseline_type_); - box = box_states_->OnCloseTag(&line_box_, box, baseline_type_, - item.HasEndEdge()); + box = HandleCloseTag(item, item_result, box); } else if (item.Type() == NGInlineItem::kAtomicInline) { - box = PlaceAtomicInline(item, &item_result, *line_info); + box = PlaceAtomicInline(item, *line_info, &item_result); } else if (item.Type() == NGInlineItem::kListMarker) { PlaceListMarker(item, &item_result, *line_info); } else if (item.Type() == NGInlineItem::kOutOfFlowPositioned) { @@ -300,14 +311,11 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info, box_states_->OnEndPlaceItems(&line_box_, baseline_type_); - // TODO(kojii): For LTR, we can optimize ComputeInlinePositions() to compute - // without PrepareForReorder() and UpdateAfterReorder() even when - // HasBoxFragments(). We do this to share the logic between LTR and RTL, and - // to get more coverage for RTL, but when we're more stabilized, we could have - // optimized code path for LTR. - box_states_->PrepareForReorder(&line_box_); - BidiReorder(); - box_states_->UpdateAfterReorder(&line_box_); + if (UNLIKELY(Node().IsBidiEnabled())) { + box_states_->PrepareForReorder(&line_box_); + BidiReorder(); + box_states_->UpdateAfterReorder(&line_box_); + } LayoutUnit inline_size = box_states_->ComputeInlinePositions(&line_box_); // Truncate the line if 'text-overflow: ellipsis' is set. @@ -350,8 +358,11 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info, // 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()) + if (line_info->IsEmptyLine()) { + container_builder_.SetIsEmptyLineBox(); + container_builder_.AddChildren(line_box_); return; + } DCHECK(!line_box_metrics.IsEmpty()); @@ -399,7 +410,7 @@ void NGInlineLayoutAlgorithm::PlaceControlItem(const NGInlineItem& item, DCHECK(item.GetLayoutObject()); DCHECK(item.GetLayoutObject()->IsText()); - if (quirks_mode_ && !box->HasMetrics()) + if (UNLIKELY(quirks_mode_ && !box->HasMetrics())) box->EnsureTextMetrics(*item.Style(), baseline_type_); NGTextFragmentBuilder text_builder(Node(), @@ -420,7 +431,7 @@ void NGInlineLayoutAlgorithm::PlaceGeneratedContent( : fragment->Size().height; const ComputedStyle& style = fragment->Style(); if (box->CanAddTextOfStyle(style)) { - if (quirks_mode_) + if (UNLIKELY(quirks_mode_)) box->EnsureTextMetrics(style, baseline_type_); DCHECK(!box->text_metrics.IsEmpty()); line_box_.AddChild(std::move(fragment), box->text_top, inline_size, @@ -440,8 +451,8 @@ void NGInlineLayoutAlgorithm::PlaceGeneratedContent( NGInlineBoxState* NGInlineLayoutAlgorithm::PlaceAtomicInline( const NGInlineItem& item, - NGInlineItemResult* item_result, - const NGLineInfo& line_info) { + const NGLineInfo& line_info, + NGInlineItemResult* item_result) { DCHECK(item_result->layout_result); // The input |position| is the line-left edge of the margin box. @@ -488,48 +499,47 @@ void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects( TextDirection line_direction = line_info.BaseDirection(); 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 (!box->StyleRef().IsOriginalDisplayInlineType()) { - LayoutUnit inline_offset = container_builder_.BfcLineOffset() - - ConstraintSpace().BfcOffset().line_offset; - - // Flip the inline_offset if we are in RTL. - if (IsRtl(line_direction)) { - LayoutUnit container_inline_size = - ConstraintSpace().AvailableSize().inline_size; - inline_offset = container_inline_size - inline_offset + inline_size; - } - - inline_offset += line_info.TextIndent(); - - // We need to subtract the line offset, in order to ignore - // floats and text-indent. - static_offset.inline_offset = -inline_offset; - - if (child.offset.inline_offset && !line_box_metrics.IsEmpty()) - static_offset.block_offset = line_box_metrics.LineHeight(); - } else { - // Our child offset is line-relative, but the static offset is - // flow-relative, using the direction we give to - // |AddInlineOutOfFlowChildCandidate|. - if (IsRtl(line_direction)) { - static_offset.inline_offset = - inline_size - static_offset.inline_offset; - } + LayoutObject* box = child.out_of_flow_positioned_box; + if (!box) + continue; + + // 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 (!box->StyleRef().IsOriginalDisplayInlineType()) { + LayoutUnit inline_offset = container_builder_.BfcLineOffset() - + ConstraintSpace().BfcOffset().line_offset; + + // Flip the inline_offset if we are in RTL. + if (IsRtl(line_direction)) { + LayoutUnit container_inline_size = + ConstraintSpace().AvailableSize().inline_size; + inline_offset = container_inline_size - inline_offset + inline_size; } - container_builder_.AddInlineOutOfFlowChildCandidate( - NGBlockNode(ToLayoutBox(box)), static_offset, line_direction, - child.out_of_flow_containing_box); + inline_offset += line_info.TextIndent(); + + // We need to subtract the line offset, in order to ignore floats and + // text-indent. + static_offset.inline_offset = -inline_offset; - child.out_of_flow_positioned_box = child.out_of_flow_containing_box = - nullptr; + if (child.offset.inline_offset && !line_box_metrics.IsEmpty()) + static_offset.block_offset = line_box_metrics.LineHeight(); + } else if (IsRtl(line_direction)) { + // Our child offset is line-relative, but the static offset is + // flow-relative, using the direction we give to + // |AddInlineOutOfFlowChildCandidate|. + static_offset.inline_offset = inline_size - static_offset.inline_offset; } + + container_builder_.AddInlineOutOfFlowChildCandidate( + NGBlockNode(ToLayoutBox(box)), static_offset, line_direction, + child.out_of_flow_containing_box); + + child.out_of_flow_positioned_box = nullptr; + child.out_of_flow_containing_box = nullptr; } } @@ -537,7 +547,7 @@ void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects( void NGInlineLayoutAlgorithm::PlaceListMarker(const NGInlineItem& item, NGInlineItemResult* item_result, const NGLineInfo& line_info) { - if (quirks_mode_) { + if (UNLIKELY(quirks_mode_)) { box_states_->LineBoxState().EnsureTextMetrics(*item.Style(), baseline_type_); } @@ -577,20 +587,18 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(NGLineInfo* line_info) { if (item_result.has_only_trailing_spaces) break; if (item_result.shape_result) { - // Mutate the existing shape result if only used here, if not create a - // copy. scoped_refptr<ShapeResult> shape_result = - item_result.shape_result->MutableUnique(); + item_result.shape_result->CreateShapeResult(); DCHECK_GE(item_result.start_offset, line_info->StartOffset()); // |shape_result| has more characters if it's hyphenated. DCHECK(item_result.text_end_effect != NGTextEndEffect::kNone || shape_result->NumCharacters() == item_result.end_offset - item_result.start_offset); - shape_result->ApplySpacing( - spacing, item_result.start_offset - line_info->StartOffset() - - shape_result->StartIndexForResult()); + shape_result->ApplySpacing(spacing, item_result.start_offset - + line_info->StartOffset() - + shape_result->StartIndex()); item_result.inline_size = shape_result->SnappedWidth(); - item_result.shape_result = std::move(shape_result); + item_result.shape_result = ShapeResultView::Create(shape_result.get()); } else if (item_result.item->Type() == NGInlineItem::kAtomicInline) { float offset = 0.f; DCHECK_LE(line_info->StartOffset(), item_result.start_offset); @@ -640,8 +648,9 @@ LayoutUnit NGInlineLayoutAlgorithm::ComputeContentSize( if (layout_object && layout_object->IsBR()) { NGBfcOffset bfc_offset = {ContainerBfcOffset().line_offset, ContainerBfcOffset().block_offset + content_size}; - AdjustToClearance(exclusion_space.ClearanceOffset(item.Style()->Clear()), - &bfc_offset); + AdjustToClearance( + exclusion_space.ClearanceOffset(ResolvedClear(*item.Style(), Style())), + &bfc_offset); content_size = bfc_offset.block_offset - ContainerBfcOffset().block_offset; } @@ -725,8 +734,8 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { #endif // Reset any state that may have been modified in a previous pass. - positioned_floats.clear(); - unpositioned_floats_.clear(); + positioned_floats.Shrink(0); + unpositioned_floats_.Shrink(0); container_builder_.Reset(); exclusion_space = initial_exclusion_space; @@ -832,7 +841,7 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { DCHECK(unpositioned_floats_.IsEmpty() || is_empty_inline); container_builder_.SwapPositionedFloats(&positioned_floats_); container_builder_.SetExclusionSpace(std::move(exclusion_space)); - container_builder_.MoveOutOfFlowDescendantCandidatesToDescendants(nullptr); + container_builder_.MoveOutOfFlowDescendantCandidatesToDescendants(); return container_builder_.ToLineBoxFragment(); } @@ -849,9 +858,9 @@ unsigned NGInlineLayoutAlgorithm::PositionLeadingFloats( if (item.Type() == NGInlineItem::kFloating) { NGBlockNode node(ToLayoutBox(item.GetLayoutObject())); - AddUnpositionedFloat( - &unpositioned_floats_, &container_builder_, - NGUnpositionedFloat(node, /* break_token */ nullptr)); + AddUnpositionedFloat(&unpositioned_floats_, &container_builder_, + NGUnpositionedFloat(node, /* break_token */ nullptr), + ConstraintSpace()); } // Abort if we've found something that makes this a non-empty inline. @@ -876,7 +885,7 @@ void NGInlineLayoutAlgorithm::PositionPendingFloats( << "The floats BFC block offset should be known here"; if (BreakToken() && BreakToken()->IgnoreFloats()) { - unpositioned_floats_.clear(); + unpositioned_floats_.Shrink(0); return; } @@ -888,15 +897,15 @@ void NGInlineLayoutAlgorithm::PositionPendingFloats( NGBfcOffset origin_bfc_offset = {ConstraintSpace().BfcOffset().line_offset, bfc_block_offset + content_size}; - const NGPositionedFloatVector positioned_floats = - PositionFloats(ConstraintSpace().AvailableSize(), - ConstraintSpace().PercentageResolutionSize(), - ConstraintSpace().ReplacedPercentageResolutionSize(), - origin_bfc_offset, bfc_block_offset, unpositioned_floats_, - ConstraintSpace(), exclusion_space); + NGPositionedFloatVector positioned_floats; + PositionFloats(ConstraintSpace().AvailableSize(), + ConstraintSpace().PercentageResolutionSize(), + ConstraintSpace().ReplacedPercentageResolutionSize(), + origin_bfc_offset, unpositioned_floats_, ConstraintSpace(), + Style(), exclusion_space, &positioned_floats); positioned_floats_.AppendVector(positioned_floats); - unpositioned_floats_.clear(); + unpositioned_floats_.Shrink(0); } void NGInlineLayoutAlgorithm::BidiReorder() { 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 e5f89bf3b6a..a65f542436a 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 @@ -8,8 +8,8 @@ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" -#include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h" #include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float_vector.h" @@ -69,6 +69,9 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final NGInlineBoxState* HandleOpenTag(const NGInlineItem&, const NGInlineItemResult&, NGInlineLayoutStateStack*) const; + NGInlineBoxState* HandleCloseTag(const NGInlineItem&, + const NGInlineItemResult&, + NGInlineBoxState*); void BidiReorder(); @@ -80,8 +83,8 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final UBiDiLevel, NGInlineBoxState*); NGInlineBoxState* PlaceAtomicInline(const NGInlineItem&, - NGInlineItemResult*, - const NGLineInfo&); + const NGLineInfo&, + NGInlineItemResult*); void PlaceLayoutResult(NGInlineItemResult*, NGInlineBoxState*, LayoutUnit inline_offset = LayoutUnit()); 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 73ab95e3d80..4585f67824a 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 @@ -44,30 +44,27 @@ TEST_F(NGInlineLayoutAlgorithmTest, BreakToken) { NGConstraintSpace constraint_space = NGConstraintSpaceBuilder( - WritingMode::kHorizontalTb, - /* icb_size */ size.ConvertToPhysical(WritingMode::kHorizontalTb)) + WritingMode::kHorizontalTb, WritingMode::kHorizontalTb, + /* is_new_fc */ false) .SetAvailableSize(size) - .ToConstraintSpace(WritingMode::kHorizontalTb); + .ToConstraintSpace(); NGInlineChildLayoutContext context; scoped_refptr<NGLayoutResult> layout_result = inline_node.Layout(constraint_space, nullptr, &context); - auto* line1 = - ToNGPhysicalLineBoxFragment(layout_result->PhysicalFragment().get()); + auto* line1 = ToNGPhysicalLineBoxFragment(layout_result->PhysicalFragment()); EXPECT_FALSE(line1->BreakToken()->IsFinished()); // Perform 2nd layout with the break token from the 1st line. scoped_refptr<NGLayoutResult> layout_result2 = inline_node.Layout(constraint_space, line1->BreakToken(), &context); - auto* line2 = - ToNGPhysicalLineBoxFragment(layout_result2->PhysicalFragment().get()); + auto* line2 = ToNGPhysicalLineBoxFragment(layout_result2->PhysicalFragment()); EXPECT_FALSE(line2->BreakToken()->IsFinished()); // Perform 3rd layout with the break token from the 2nd line. scoped_refptr<NGLayoutResult> layout_result3 = inline_node.Layout(constraint_space, line2->BreakToken(), &context); - auto* line3 = - ToNGPhysicalLineBoxFragment(layout_result3->PhysicalFragment().get()); + auto* line3 = ToNGPhysicalLineBoxFragment(layout_result3->PhysicalFragment()); EXPECT_TRUE(line3->BreakToken()->IsFinished()); } @@ -129,6 +126,51 @@ TEST_F(NGInlineLayoutAlgorithmTest, GenerateEllipsis) { EXPECT_EQ(line1.Children()[0]->GetLayoutObject(), ellipsis.GetLayoutObject()); } +// This test ensures box fragments are generated when necessary, even when the +// line is empty. One such case is when the line contains a containing box of an +// out-of-flow object. +TEST_F(NGInlineLayoutAlgorithmTest, + EmptyLineWithOutOfFlowInInlineContainingBlock) { + SetBodyInnerHTML(R"HTML( + <!DOCTYPE html> + <style> + oof-container { + position: relative; + } + oof { + position: absolute; + width: 100px; + height: 100px; + } + html, body { margin: 0; } + html { + font-size: 10px; + } + </style> + <div id=container> + <oof-container> + <oof></oof> + </oof-container> + </div> + )HTML"); + LayoutBlockFlow* block_flow = + ToLayoutBlockFlow(GetLayoutObjectByElementId("container")); + const NGPhysicalBoxFragment* container = block_flow->CurrentFragment(); + ASSERT_TRUE(container); + EXPECT_EQ(LayoutUnit(), container->Size().height); + + EXPECT_EQ(2u, container->Children().size()); + const NGPhysicalLineBoxFragment& linebox = + ToNGPhysicalLineBoxFragment(*container->Children()[0]); + + EXPECT_EQ(1u, linebox.Children().size()); + EXPECT_EQ(NGPhysicalSize(), linebox.Size()); + + const NGPhysicalBoxFragment& oof_container = + ToNGPhysicalBoxFragment(*linebox.Children()[0]); + EXPECT_EQ(NGPhysicalSize(), oof_container.Size()); +} + // This test ensures that if an inline box generates (or does not generate) box // fragments for a wrapped line, it should consistently do so for other lines // too, when the inline box is fragmented to multiple lines. @@ -177,6 +219,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, ContainerBorderPadding) { div { padding-left: 5px; padding-top: 10px; + display: flow-root; } </style> <div id=container>test</div> @@ -188,8 +231,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, ContainerBorderPadding) { NGConstraintSpace::CreateFromLayoutObject(*block_flow); scoped_refptr<NGLayoutResult> layout_result = block_node.Layout(space); - auto* block_box = - ToNGPhysicalBoxFragment(layout_result->PhysicalFragment().get()); + auto* block_box = ToNGPhysicalBoxFragment(layout_result->PhysicalFragment()); EXPECT_TRUE(layout_result->BfcBlockOffset().has_value()); EXPECT_EQ(0, layout_result->BfcBlockOffset().value()); EXPECT_EQ(0, layout_result->BfcLineOffset()); @@ -223,8 +265,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, MAYBE_VerticalAlignBottomReplaced) { scoped_refptr<NGLayoutResult> layout_result = inline_node.Layout(space, nullptr, &context); - auto* line = - ToNGPhysicalLineBoxFragment(layout_result->PhysicalFragment().get()); + auto* line = ToNGPhysicalLineBoxFragment(layout_result->PhysicalFragment()); EXPECT_EQ(LayoutUnit(96), line->Size().height); NGPhysicalOffset img_offset = line->Children()[0].Offset(); EXPECT_EQ(LayoutUnit(0), img_offset.top); @@ -441,6 +482,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, InkOverflow) { <style> #container { font: 20px/.5 Ahem; + display: flow-root; } </style> <div id="container">Hello</div> 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 24322a11fad..a9ee04102d0 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 @@ -11,6 +11,7 @@ #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/logical_values.h" #include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.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" @@ -29,10 +30,12 @@ #include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h" #include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h" +#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" #include "third_party/blink/renderer/core/style/computed_style.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/fonts/shaping/shape_result_view.h" #include "third_party/blink/renderer/platform/wtf/text/character_names.h" namespace blink { @@ -74,8 +77,8 @@ void ClearNeedsLayout(LayoutObject* object) { // Reset previous items if they cannot be reused to prevent stale items // for subsequent layouts. Items that can be reused have already been // added to the builder. - if (object->IsLayoutNGText()) - ToLayoutNGText(object)->ClearInlineItems(); + if (object->IsText()) + ToLayoutText(object)->ClearInlineItems(); } // The function is templated to indicate the purpose of collected inlines: @@ -108,11 +111,8 @@ void CollectInlinesInternal( // if the last ended with space and this starts with space, do not allow // reuse. builder->MightCollapseWithPreceding(*previous_text) bool item_reused = false; - if (node->IsLayoutNGText() && ToLayoutNGText(node)->HasValidLayout() && - previous_text) { - item_reused = builder->Append(*previous_text, ToLayoutNGText(node), - ToLayoutNGText(node)->InlineItems()); - } + if (previous_text && layout_text->HasValidInlineItems()) + item_reused = builder->Append(*previous_text, layout_text); // If not create a new item as needed. if (!item_reused) { @@ -160,20 +160,23 @@ void CollectInlinesInternal( // should not appear. LayoutObject tree should have created an anonymous // box to prevent having inline/block-mixed children. DCHECK(node->IsInline()); + LayoutInline* layout_inline = ToLayoutInline(node); + if (update_layout) + layout_inline->UpdateShouldCreateBoxFragment(); - builder->EnterInline(node); + builder->EnterInline(layout_inline); // Traverse to children if they exist. - if (LayoutObject* child = node->SlowFirstChild()) { + if (LayoutObject* child = layout_inline->FirstChild()) { node = child; continue; } // An empty inline node. - builder->ExitInline(node); + builder->ExitInline(layout_inline); if (update_layout) - ClearNeedsLayout(node); + ClearNeedsLayout(layout_inline); } // Find the next sibling, or parent, until we reach |block|. @@ -284,32 +287,72 @@ const NGOffsetMapping* NGInlineNode::ComputeOffsetMappingIfNeeded() { NGInlineNodeData* data = MutableData(); if (!data->offset_mapping) { - // TODO(xiaochengh): ComputeOffsetMappingIfNeeded() discards the - // NGInlineItems and text content built by |builder|, because they are - // already there in NGInlineNodeData. For efficiency, we should make - // |builder| not construct items and text content. - Vector<NGInlineItem> items; - items.ReserveCapacity(EstimateInlineItemsCount(*GetLayoutBlockFlow())); - NGInlineItemsBuilderForOffsetMapping builder(&items); - builder.GetOffsetMappingBuilder().ReserveCapacity( - EstimateOffsetMappingItemsCount(*GetLayoutBlockFlow())); - const bool update_layout = false; - CollectInlinesInternal(GetLayoutBlockFlow(), &builder, nullptr, - update_layout); - String text = builder.ToString(); - DCHECK_EQ(data->text_content, text); - - // TODO(xiaochengh): This doesn't compute offset mapping correctly when - // text-transform CSS property changes text length. - NGOffsetMappingBuilder& mapping_builder = builder.GetOffsetMappingBuilder(); - mapping_builder.SetDestinationString(data->text_content); - data->offset_mapping = - std::make_unique<NGOffsetMapping>(mapping_builder.Build()); + DCHECK(!data->text_content.IsNull()); + ComputeOffsetMapping(GetLayoutBlockFlow(), data); + DCHECK(data->offset_mapping); } return data->offset_mapping.get(); } +void NGInlineNode::ComputeOffsetMapping(LayoutBlockFlow* layout_block_flow, + NGInlineNodeData* data) { + DCHECK(!data->offset_mapping); + DCHECK(!layout_block_flow->GetDocument().NeedsLayoutTreeUpdate()); + + // TODO(xiaochengh): ComputeOffsetMappingIfNeeded() discards the + // NGInlineItems and text content built by |builder|, because they are + // already there in NGInlineNodeData. For efficiency, we should make + // |builder| not construct items and text content. + Vector<NGInlineItem> items; + items.ReserveCapacity(EstimateInlineItemsCount(*layout_block_flow)); + NGInlineItemsBuilderForOffsetMapping builder(&items); + builder.GetOffsetMappingBuilder().ReserveCapacity( + EstimateOffsetMappingItemsCount(*layout_block_flow)); + const bool update_layout = false; + CollectInlinesInternal(layout_block_flow, &builder, nullptr, update_layout); + + // We need the text for non-NG object. Otherwise |data| already has the text + // from the pre-layout phase, check they match. + if (data->text_content.IsNull()) + data->text_content = builder.ToString(); + else + DCHECK_EQ(data->text_content, builder.ToString()); + + // TODO(xiaochengh): This doesn't compute offset mapping correctly when + // text-transform CSS property changes text length. + NGOffsetMappingBuilder& mapping_builder = builder.GetOffsetMappingBuilder(); + mapping_builder.SetDestinationString(data->text_content); + data->offset_mapping = + std::make_unique<NGOffsetMapping>(mapping_builder.Build()); + DCHECK(data->offset_mapping); +} + +const NGOffsetMapping* NGInlineNode::GetOffsetMapping( + LayoutBlockFlow* layout_block_flow, + std::unique_ptr<NGOffsetMapping>* storage) { + DCHECK(!layout_block_flow->GetDocument().NeedsLayoutTreeUpdate()); + + // If |layout_block_flow| is LayoutNG, compute from |NGInlineNode|. + if (layout_block_flow->IsLayoutNGMixin()) { + NGInlineNode node(layout_block_flow); + if (node.IsPrepareLayoutFinished()) + return node.ComputeOffsetMappingIfNeeded(); + + // When this is not laid out yet, compute each time it is requested. + // TODO(kojii): We could still keep the result for later uses but it would + // add more states. Reconsider if this turned out to be needed. + } + + // If this is not LayoutNG, compute the offset mapping and store in |storage|. + // The caller is responsible to keep |storage| for the life cycle. + NGInlineNodeData data; + ComputeOffsetMapping(layout_block_flow, &data); + *storage = std::move(data.offset_mapping); + DCHECK(*storage); + return storage->get(); +} + // Depth-first-scan of all LayoutInline and LayoutText nodes that make up this // NGInlineNode object. Collects LayoutText items, merging them up into the // parent LayoutInline where possible, and joining all text content in a single @@ -504,9 +547,9 @@ void NGInlineNode::ShapeText(const String& text_content, if (previous_text && end_offset == start_item.EndOffset() && !NeedsShaping(start_item)) { DCHECK_EQ(start_item.StartOffset(), - start_item.TextShapeResult()->StartIndexForResult()); + start_item.TextShapeResult()->StartIndex()); DCHECK_EQ(start_item.EndOffset(), - start_item.TextShapeResult()->EndIndexForResult()); + start_item.TextShapeResult()->EndIndex()); index++; continue; } @@ -554,6 +597,7 @@ void NGInlineNode::ShapeText(const String& text_content, // If the text is from multiple items, split the ShapeResult to // corresponding items. + unsigned opaque_context = 0; for (; index < end_index; index++) { NGInlineItem& item = (*items)[index]; if (item.Type() != NGInlineItem::kText) @@ -565,8 +609,8 @@ void NGInlineNode::ShapeText(const String& text_content, // // When multiple code units shape to one glyph, such as ligatures, the // item that has its first code unit keeps the glyph. - item.shape_result_ = - shape_result->SubRange(item.StartOffset(), item.EndOffset()); + item.shape_result_ = shape_result->SubRange( + item.StartOffset(), item.EndOffset(), &opaque_context); } } } @@ -622,7 +666,7 @@ void NGInlineNode::ShapeTextForFirstLineIfNeeded(NGInlineNodeData* data) { item.layout_object_->IsLayoutInline() && item.layout_object_->Parent() == GetLayoutBox() && ToLayoutInline(item.layout_object_)->IsFirstLineAnonymous()) { - item.should_create_box_fragment_ = true; + item.SetShouldCreateBoxFragment(); } break; } @@ -641,8 +685,10 @@ void NGInlineNode::AssociateItemsWithInlines(NGInlineNodeData* data) { LayoutObject* last_object = nullptr; for (auto& item : data->items) { LayoutObject* object = item.GetLayoutObject(); - if (object && object->IsLayoutNGText()) { - LayoutNGText* layout_text = ToLayoutNGText(object); + if (!object) + continue; + if (object->IsText()) { + LayoutText* layout_text = ToLayoutText(object); if (object != last_object) layout_text->ClearInlineItems(); layout_text->AddInlineItem(&item); @@ -693,6 +739,120 @@ scoped_refptr<NGLayoutResult> NGInlineNode::Layout( return algorithm.Layout(); } +bool NGInlineNode::PrepareReuseFragments( + const NGConstraintSpace& constraint_space) { + if (!IsPrepareLayoutFinished()) + return false; + + LayoutBlockFlow* block_flow = GetLayoutBlockFlow(); + if (!block_flow->EverHadLayout()) + return false; + + // If the block flow itself was changed, re-layout may be needed. + if (block_flow->SelfNeedsLayout()) + return false; + + // Check the cached result is valid for the constraint space. + if (!block_flow->AreCachedLinesValidFor(constraint_space)) + return false; + + if (!MarkLineBoxesDirty(block_flow)) + return false; + + PrepareLayoutIfNeeded(); + + return true; +} + +// Mark the first line box that have |NeedsLayout()| dirty. +// +// Removals of LayoutObject already marks relevant line boxes dirty by calling +// |DirtyLinesFromChangedChild()|, but insertions and style changes are not +// marked yet. +bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow) { + DCHECK(block_flow); + DCHECK(block_flow->PaintFragment()); + bool has_dirtied_lines = false; + NGPaintFragment* last_fragment = nullptr; + for (LayoutObject* layout_object = block_flow->NextInPreOrder(block_flow); + layout_object;) { + bool should_dirty_lines = false; + NGPaintFragment* fragment = nullptr; + LayoutObject* next = nullptr; + if (LayoutText* layout_text = ToLayoutTextOrNull(layout_object)) { + if (!has_dirtied_lines) { + should_dirty_lines = layout_object->SelfNeedsLayout(); + if (!should_dirty_lines) + fragment = layout_text->FirstInlineFragment(); + } + next = layout_object->NextInPreOrderAfterChildren(block_flow); + layout_object->ClearNeedsLayout(); + } else if (LayoutInline* layout_inline = + ToLayoutInlineOrNull(layout_object)) { + if (!has_dirtied_lines) { + should_dirty_lines = layout_object->SelfNeedsLayout(); + // Do not keep fragments of LayoutInline unless it's a leaf, because + // the last fragment of LayoutInline is not the previous fragment of its + // descendants. + if (!should_dirty_lines && !layout_inline->FirstChild()) + fragment = layout_inline->FirstInlineFragment(); + } + next = layout_object->NextInPreOrder(block_flow); + layout_object->ClearNeedsLayout(); + } else if (UNLIKELY(layout_object->IsFloatingOrOutOfFlowPositioned())) { + // Aborting in the middle of the traversal is safe because this function + // ClearNeedsLayout() on text and LayoutInline, but since an inline + // formatting context is laid out as a whole, these flags don't matter. + // For that reason, this traversal should not ClearNeedsLayout() atomic + // inlines, floats, or OOF -- objects that need to be laid out separately + // from the inline formatting context. + // TODO(kojii): This looks a bit tricky, better to come up with clearner + // solution if any. + return false; + } else if (layout_object->IsAtomicInlineLevel()) { + if (!has_dirtied_lines) { + should_dirty_lines = layout_object->NeedsLayout(); + if (!should_dirty_lines) + fragment = layout_object->FirstInlineFragment(); + } + next = layout_object->NextInPreOrderAfterChildren(block_flow); + } else { + NOTREACHED(); + // With LayoutNGBlockFragmentation, LayoutFlowThread/LayoutMultiColumnSet + // appear in fast/multicol/paged-becomes-multicol-auto-height.html. + // crbug.com/897141 + next = layout_object->NextInPreOrder(block_flow); + } + + if (!has_dirtied_lines) { + if (should_dirty_lines) { + if (last_fragment) { + // Changes in this LayoutObject may affect the line that contains its + // previous object. Mark the line box that contains the last fragment + // of the previous object. + last_fragment->LastForSameLayoutObject() + ->MarkContainingLineBoxDirty(); + } else { + // If there were no fragments so far in this pre-order traversal, mark + // the first line box dirty. + NGPaintFragment* block_fragment = block_flow->PaintFragment(); + DCHECK(block_fragment); + if (NGPaintFragment* first_line = block_fragment->FirstLineBox()) + first_line->MarkLineBoxDirty(); + } + has_dirtied_lines = true; + } else if (fragment) { + last_fragment = fragment; + } + } + + ClearInlineFragment(layout_object); + layout_object = next; + } + block_flow->ClearNeedsLayout(); + return true; +} + static LayoutUnit ComputeContentSize( NGInlineNode node, WritingMode container_writing_mode, @@ -704,20 +864,14 @@ static LayoutUnit ComputeContentSize( LayoutUnit available_inline_size = mode == NGLineBreakerMode::kMaxContent ? LayoutUnit::Max() : LayoutUnit(); - NGPhysicalSize icb_size = constraint_space - ? constraint_space->InitialContainingBlockSize() - : node.InitialContainingBlockSize(); - DCHECK(!constraint_space || constraint_space->InitialContainingBlockSize() == - node.InitialContainingBlockSize()) - << constraint_space->InitialContainingBlockSize() << " vs " - << node.InitialContainingBlockSize(); - NGConstraintSpace space = - NGConstraintSpaceBuilder(writing_mode, icb_size) + NGConstraintSpaceBuilder(/* parent_writing_mode */ writing_mode, + /* out_writing_mode */ writing_mode, + /* is_new_fc */ false) .SetTextDirection(style.Direction()) .SetAvailableSize({available_inline_size, NGSizeIndefinite}) .SetIsIntermediateLayout(true) - .ToConstraintSpace(writing_mode); + .ToConstraintSpace(); Vector<NGPositionedFloat> positioned_floats; NGUnpositionedFloatVector unpositioned_floats; @@ -733,7 +887,7 @@ static LayoutUnit ComputeContentSize( nullptr /* container_builder */, &empty_exclusion_space, 0u, line_opportunity, nullptr /* break_token */); do { - unpositioned_floats.clear(); + unpositioned_floats.Shrink(0); NGLineInfo line_info; line_breaker.NextLine(&line_info); @@ -761,27 +915,15 @@ static LayoutUnit ComputeContentSize( const ComputedStyle& float_style = float_node.Style(); MinMaxSizeInput zero_input; // Floats don't intrude into floats. - // We'll need extrinsic sizing data when computing min/max for orthogonal - // flow roots. - NGConstraintSpace extrinsic_constraint_space; - const NGConstraintSpace* optional_constraint_space = nullptr; - if (!IsParallelWritingMode(container_writing_mode, - float_node.Style().GetWritingMode())) { - DCHECK(constraint_space); - extrinsic_constraint_space = CreateExtrinsicConstraintSpaceForChild( - *constraint_space, input.extrinsic_block_size, float_node); - optional_constraint_space = &extrinsic_constraint_space; - } - - MinMaxSize child_sizes = ComputeMinAndMaxContentContribution( - writing_mode, float_node, zero_input, optional_constraint_space); + MinMaxSize child_sizes = + ComputeMinAndMaxContentContribution(style, float_node, zero_input); LayoutUnit child_inline_margins = ComputeMinMaxMargins(style, float_node).InlineSum(); if (mode == NGLineBreakerMode::kMinContent) { result = std::max(result, child_sizes.min_size + child_inline_margins); } else { - const EClear float_clear = float_style.Clear(); + const EClear float_clear = ResolvedClear(float_style, style); // If this float clears the previous float we start a new "line". // This is subtly different to block layout which will only reset either @@ -798,7 +940,7 @@ static LayoutUnit ComputeContentSize( // 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(); + previous_float_type = ResolvedFloating(float_style, style); } } 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 57c0776bbd9..1e38711ccd3 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 @@ -45,6 +45,9 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { const NGBreakToken*, NGInlineChildLayoutContext* context); + // Prepare to reuse fragments. Returns false if reuse is not possible. + bool PrepareReuseFragments(const NGConstraintSpace&); + // Computes the value of min-content and max-content for this anonymous block // box. min-content is the inline size when lines wrap at every break // opportunity, and max-content is when lines do not wrap at all. @@ -67,6 +70,17 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { // This funciton must be called with clean layout. const NGOffsetMapping* ComputeOffsetMappingIfNeeded(); + // Get |NGOffsetMapping| for the |layout_block_flow|. If |layout_block_flow| + // is LayoutNG and it is already laid out, this function is the same as + // |ComputeOffsetMappingIfNeeded|. |storage| is not used in this case. + // + // Otherwise, this function computes |NGOffsetMapping| and store in |storage| + // as well as returning the pointer. The caller is responsible for keeping + // |storage| for the life cycle of the returned |NGOffsetMapping|. + static const NGOffsetMapping* GetOffsetMapping( + LayoutBlockFlow* layout_block_flow, + std::unique_ptr<NGOffsetMapping>* storage); + bool IsBidiEnabled() const { return Data().is_bidi_enabled_; } TextDirection BaseDirection() const { return Data().BaseDirection(); } @@ -106,6 +120,8 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { void ClearAssociatedFragments(const NGInlineBreakToken*); + bool MarkLineBoxesDirty(LayoutBlockFlow*); + NGInlineNodeData* MutableData() { return ToLayoutBlockFlow(box_)->GetNGInlineNodeData(); } @@ -116,6 +132,9 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { } const NGInlineNodeData& EnsureData(); + static void ComputeOffsetMapping(LayoutBlockFlow* layout_block_flow, + NGInlineNodeData* data); + friend class NGLineBreakerTest; friend class NGInlineNodeLegacy; }; 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 d095df4757c..788aecf9934 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 @@ -16,6 +16,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" +#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" #include "third_party/blink/renderer/core/style/computed_style.h" namespace blink { @@ -67,6 +68,10 @@ class NGInlineNodeForTest : public NGInlineNode { void CollectInlines() { NGInlineNode::CollectInlines(MutableData()); } void ShapeText() { NGInlineNode::ShapeText(MutableData()); } + + bool MarkLineBoxesDirty() { + return NGInlineNode::MarkLineBoxesDirty(GetLayoutBlockFlow()); + } }; class NGInlineNodeTest : public NGLayoutTest { @@ -102,12 +107,12 @@ class NGInlineNodeTest : public NGLayoutTest { void CreateLine( NGInlineNode node, Vector<scoped_refptr<const NGPhysicalTextFragment>>* fragments_out) { - NGPhysicalSize icb_size(LayoutUnit(200), LayoutUnit(200)); - NGConstraintSpace constraint_space = - NGConstraintSpaceBuilder(WritingMode::kHorizontalTb, icb_size) + NGConstraintSpaceBuilder(WritingMode::kHorizontalTb, + WritingMode::kHorizontalTb, + /* is_new_fc */ false) .SetAvailableSize({LayoutUnit::Max(), LayoutUnit(-1)}) - .ToConstraintSpace(WritingMode::kHorizontalTb); + .ToConstraintSpace(); NGInlineChildLayoutContext context; scoped_refptr<NGLayoutResult> result = NGInlineLayoutAlgorithm(node, constraint_space, @@ -115,7 +120,7 @@ class NGInlineNodeTest : public NGLayoutTest { .Layout(); const NGPhysicalLineBoxFragment* line = - ToNGPhysicalLineBoxFragment(result->PhysicalFragment().get()); + ToNGPhysicalLineBoxFragment(result->PhysicalFragment()); for (const auto& child : line->Children()) { fragments_out->push_back(ToNGPhysicalTextFragment(child.get())); } @@ -127,6 +132,24 @@ class NGInlineNodeTest : public NGLayoutTest { return data->text_content; } + // Mark line boxes dirty and returns child paint fragments of + // |layout_block_flow_|. + Vector<NGPaintFragment*, 16> MarkLineBoxesDirty() const { + // Attach new LayoutObjects if there were any, but do not run layout, + // because running layout will re-create fragments. + GetDocument().UpdateStyleAndLayoutTree(); + + NGInlineNodeForTest node(layout_block_flow_); + EXPECT_TRUE(node.MarkLineBoxesDirty()); + + scoped_refptr<const NGPaintFragment> fragment = + layout_block_flow_->PaintFragment(); + EXPECT_TRUE(fragment); + Vector<NGPaintFragment*, 16> children; + fragment->Children().ToList(&children); + return children; + } + Vector<NGInlineItem>& Items() { NGInlineNodeData* data = layout_block_flow_->GetNGInlineNodeData(); CHECK(data); @@ -467,13 +490,26 @@ TEST_F(NGInlineNodeTest, MinMaxSizeFloatsClearance) { EXPECT_EQ(160, sizes.max_size); } +TEST_F(NGInlineNodeTest, AssociatedItemsWithControlItem) { + SetBodyInnerHTML( + "<pre id=t style='-webkit-rtl-ordering:visual'>ab\nde</pre>"); + LayoutText* const layout_text = ToLayoutText( + GetDocument().getElementById("t")->firstChild()->GetLayoutObject()); + ASSERT_TRUE(layout_text->HasValidInlineItems()); + const Vector<NGInlineItem*>& items = layout_text->InlineItems(); + ASSERT_EQ(3u, items.size()); + TEST_ITEM_TYPE_OFFSET((*items[0]), kText, 1u, 3u); + TEST_ITEM_TYPE_OFFSET((*items[1]), kControl, 4u, 5u); + TEST_ITEM_TYPE_OFFSET((*items[2]), kText, 6u, 8u); +} + TEST_F(NGInlineNodeTest, InvalidateAddSpan) { SetupHtml("t", "<div id=t>before</div>"); EXPECT_FALSE(layout_block_flow_->NeedsCollectInlines()); unsigned item_count_before = Items().size(); Element* parent = ToElement(layout_block_flow_->GetNode()); - Element* span = GetDocument().CreateRawElement(HTMLNames::spanTag); + Element* span = GetDocument().CreateRawElement(html_names::kSpanTag); parent->appendChild(span); // NeedsCollectInlines() is marked during the layout. @@ -499,7 +535,7 @@ TEST_F(NGInlineNodeTest, InvalidateAddInnerSpan) { Element* parent = GetElementById("x"); ASSERT_TRUE(parent); - Element* span = GetDocument().CreateRawElement(HTMLNames::spanTag); + Element* span = GetDocument().CreateRawElement(html_names::kSpanTag); parent->appendChild(span); // NeedsCollectInlines() is marked during the layout. @@ -544,7 +580,7 @@ TEST_F(NGInlineNodeTest, InvalidateAddAbsolute) { unsigned item_count_before = Items().size(); Element* parent = ToElement(layout_block_flow_->GetNode()); - Element* span = GetDocument().CreateRawElement(HTMLNames::spanTag); + Element* span = GetDocument().CreateRawElement(html_names::kSpanTag); parent->appendChild(span); // NeedsCollectInlines() is marked during the layout. @@ -609,7 +645,7 @@ TEST_F(NGInlineNodeTest, InvalidateAddFloat) { unsigned item_count_before = Items().size(); Element* parent = ToElement(layout_block_flow_->GetNode()); - Element* span = GetDocument().CreateRawElement(HTMLNames::spanTag); + Element* span = GetDocument().CreateRawElement(html_names::kSpanTag); parent->appendChild(span); // NeedsCollectInlines() is marked during the layout. @@ -645,4 +681,187 @@ TEST_F(NGInlineNodeTest, SpaceRestoredByInsertingWord) { EXPECT_EQ(String("before mid after"), GetText()); } +// Test marking line boxes when inserting a span before the first child. +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnInsert) { + SetupHtml("container", R"HTML( + <div id=container style="font-size: 10px; width: 10ch"> + 12345678 + </div> + )HTML"); + + Element* span = GetDocument().CreateElementForBinding("span"); + Element* container = GetElementById("container"); + container->insertBefore(span, container->firstChild()); + + auto lines = MarkLineBoxesDirty(); + EXPECT_TRUE(lines[0]->IsDirty()); +} + +// Test marking line boxes when appending a span. +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnAppend) { + SetupHtml("container", R"HTML( + <div id=container style="font-size: 10px; width: 10ch"> + 12345678 + </div> + )HTML"); + + Element* span = GetDocument().CreateElementForBinding("span"); + layout_block_flow_->GetNode()->appendChild(span); + + auto lines = MarkLineBoxesDirty(); + EXPECT_TRUE(lines[0]->IsDirty()); +} + +// Test marking line boxes when appending a span on 2nd line. +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnAppend2) { + SetupHtml("container", R"HTML( + <div id=container style="font-size: 10px; width: 10ch"> + 12345678 + 2234 + </div> + )HTML"); + + Element* span = GetDocument().CreateElementForBinding("span"); + layout_block_flow_->GetNode()->appendChild(span); + + auto lines = MarkLineBoxesDirty(); + EXPECT_FALSE(lines[0]->IsDirty()); + EXPECT_TRUE(lines[1]->IsDirty()); +} + +// Test marking line boxes when removing a span. +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnRemove) { + SetupHtml("container", R"HTML( + <div id=container style="font-size: 10px; width: 10ch"> + 1234<span id=t>5678</span> + </div> + )HTML"); + + Element* span = GetElementById("t"); + span->remove(); + + auto lines = MarkLineBoxesDirty(); + EXPECT_TRUE(lines[0]->IsDirty()); +} + +// Test marking line boxes when removing a span. +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnRemoveFirst) { + SetupHtml("container", R"HTML( + <div id=container style="font-size: 10px; width: 10ch"> + <span id=t>1234</span>5678 + </div> + )HTML"); + + Element* span = GetElementById("t"); + span->remove(); + + auto lines = MarkLineBoxesDirty(); + EXPECT_TRUE(lines[0]->IsDirty()); +} + +// Test marking line boxes when removing a span on 2nd line. +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnRemove2) { + SetupHtml("container", R"HTML( + <div id=container style="font-size: 10px; width: 10ch"> + 12345678 + 2234<span id=t>5678 3334</span> + </div> + )HTML"); + + Element* span = GetElementById("t"); + span->remove(); + + auto lines = MarkLineBoxesDirty(); + EXPECT_FALSE(lines[0]->IsDirty()); + EXPECT_TRUE(lines[1]->IsDirty()); +} + +// Test marking line boxes when the first span has NeedsLayout. The span is +// culled. +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutFirst) { + SetupHtml("container", R"HTML( + <div id=container style="font-size: 10px; width: 10ch"> + <span id=t>1234</span>5678 + </div> + )HTML"); + + LayoutObject* span = GetLayoutObjectByElementId("t"); + span->SetNeedsLayout(""); + + auto lines = MarkLineBoxesDirty(); + EXPECT_TRUE(lines[0]->IsDirty()); +} + +// Test marking line boxes when the first span has NeedsLayout. The span has a +// box fragment. +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutFirstWithBox) { + SetupHtml("container", R"HTML( + <div id=container style="font-size: 10px; width: 10ch"> + <span id=t style="background: blue">1234</span>5678 + </div> + )HTML"); + + LayoutObject* span = GetLayoutObjectByElementId("t"); + span->SetNeedsLayout(""); + + auto lines = MarkLineBoxesDirty(); + EXPECT_TRUE(lines[0]->IsDirty()); +} + +// Test marking line boxes when a span has NeedsLayout. The span is culled. +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayout) { + SetupHtml("container", R"HTML( + <div id=container style="font-size: 10px; width: 10ch"> + 12345678 + 2234<span id=t>5678 3334</span> + </div> + )HTML"); + + LayoutObject* span = GetLayoutObjectByElementId("t"); + span->SetNeedsLayout(""); + + auto lines = MarkLineBoxesDirty(); + EXPECT_FALSE(lines[0]->IsDirty()); + EXPECT_TRUE(lines[1]->IsDirty()); +} + +// Test marking line boxes when a span has NeedsLayout. The span has a box +// fragment. +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutWithBox) { + SetupHtml("container", R"HTML( + <div id=container style="font-size: 10px; width: 10ch"> + 12345678 + 2234<span id=t style="background: blue">5678 3334</span> + </div> + )HTML"); + + LayoutObject* span = GetLayoutObjectByElementId("t"); + span->SetNeedsLayout(""); + + auto lines = MarkLineBoxesDirty(); + EXPECT_FALSE(lines[0]->IsDirty()); + EXPECT_TRUE(lines[1]->IsDirty()); +} + +// Test marking line boxes when a span inside a span has NeedsLayout. +// The parent span has a box fragment, and wraps, so that its fragment +// is seen earlier in pre-order DFS. +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnChildOfWrappedBox) { + SetupHtml("container", R"HTML( + <div id=container style="font-size: 10px"> + <span style="background: yellow"> + <span id=t>target</span> + <br> + 12345678 + </span> + </div> + )HTML"); + + LayoutObject* span = GetLayoutObjectByElementId("t"); + span->SetNeedsLayout(""); + + auto lines = MarkLineBoxesDirty(); + EXPECT_TRUE(lines[0]->IsDirty()); +} + } // namespace blink 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 b9a2451e7df..d70c22a17f3 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 @@ -14,32 +14,16 @@ namespace blink { -NGLineBoxFragmentBuilder::NGLineBoxFragmentBuilder( - NGInlineNode node, - scoped_refptr<const ComputedStyle> style, - WritingMode writing_mode, - TextDirection) - : NGContainerFragmentBuilder(style, writing_mode, TextDirection::kLtr), - node_(node), - base_direction_(TextDirection::kLtr) {} - -NGLineBoxFragmentBuilder::~NGLineBoxFragmentBuilder() = default; - void NGLineBoxFragmentBuilder::Reset() { - children_.clear(); - offsets_.clear(); + children_.resize(0); + offsets_.resize(0); + line_box_type_ = NGPhysicalLineBoxFragment::kNormalLineBox; metrics_ = NGLineHeightMetrics(); size_.inline_size = LayoutUnit(); } -LayoutUnit NGLineBoxFragmentBuilder::LineHeight() const { - return metrics_.LineHeight().ClampNegativeToZero(); -} - -const NGPhysicalFragment* NGLineBoxFragmentBuilder::Child::PhysicalFragment() - const { - return layout_result ? layout_result->PhysicalFragment().get() - : fragment.get(); +void NGLineBoxFragmentBuilder::SetIsEmptyLineBox() { + line_box_type_ = NGPhysicalLineBoxFragment::kEmptyLineBox; } NGLineBoxFragmentBuilder::Child* @@ -61,16 +45,6 @@ NGLineBoxFragmentBuilder::ChildList::LastInFlowChild() { return nullptr; } -void NGLineBoxFragmentBuilder::ChildList::InsertChild( - unsigned index, - scoped_refptr<NGLayoutResult> layout_result, - const NGLogicalOffset& offset, - LayoutUnit inline_size, - UBiDiLevel bidi_level) { - children_.insert( - index, Child{std::move(layout_result), offset, inline_size, bidi_level}); -} - void NGLineBoxFragmentBuilder::ChildList::MoveInInlineDirection( LayoutUnit delta, unsigned start, @@ -92,24 +66,6 @@ void NGLineBoxFragmentBuilder::ChildList::MoveInBlockDirection(LayoutUnit delta, children_[index].offset.block_offset += delta; } -void NGLineBoxFragmentBuilder::SetMetrics(const NGLineHeightMetrics& metrics) { - metrics_ = metrics; -} - -void NGLineBoxFragmentBuilder::SetBaseDirection(TextDirection direction) { - base_direction_ = direction; -} - -void NGLineBoxFragmentBuilder::SwapPositionedFloats( - Vector<NGPositionedFloat>* positioned_floats) { - positioned_floats_.swap(*positioned_floats); -} - -void NGLineBoxFragmentBuilder::SetBreakToken( - scoped_refptr<NGInlineBreakToken> break_token) { - break_token_ = std::move(break_token); -} - void NGLineBoxFragmentBuilder::AddChildren(ChildList& children) { offsets_.ReserveCapacity(children.size()); children_.ReserveCapacity(children.size()); @@ -127,39 +83,15 @@ void NGLineBoxFragmentBuilder::AddChildren(ChildList& children) { } scoped_refptr<NGLayoutResult> NGLineBoxFragmentBuilder::ToLineBoxFragment() { - DCHECK_EQ(offsets_.size(), children_.size()); + writing_mode_ = ToLineWritingMode(writing_mode_); - WritingMode line_writing_mode(ToLineWritingMode(GetWritingMode())); - NGPhysicalSize physical_size = Size().ConvertToPhysical(line_writing_mode); - - DCHECK_EQ(children_.size(), offsets_.size()); - for (size_t i = 0; i < children_.size(); i++) { - auto& child = children_[i]; - child.offset_ = offsets_[i].ConvertToPhysical( - line_writing_mode, Direction(), physical_size, child->Size()); - } - - // Because this vector will be long-lived, make sure to not waaste space. - // (We reserve an initial capacity when adding the first child) - if (children_.size()) - children_.ShrinkToReasonableCapacity(); + if (!break_token_) + break_token_ = NGInlineBreakToken::Create(node_); scoped_refptr<const NGPhysicalLineBoxFragment> fragment = - base::AdoptRef(new NGPhysicalLineBoxFragment( - Style(), style_variant_, physical_size, children_, metrics_, - base_direction_, - break_token_ ? std::move(break_token_) - : NGInlineBreakToken::Create(node_))); - - return base::AdoptRef(new NGLayoutResult( - std::move(fragment), std::move(oof_positioned_descendants_), - std::move(positioned_floats_), unpositioned_list_marker_, - std::move(exclusion_space_), bfc_line_offset_, bfc_block_offset_, - end_margin_strut_, - /* intrinsic_block_size */ LayoutUnit(), - /* minimal_space_shortage */ LayoutUnit::Max(), EBreakBetween::kAuto, - EBreakBetween::kAuto, /* has_forced_break */ false, is_pushed_by_floats_, - adjoining_floats_, NGLayoutResult::kSuccess)); + NGPhysicalLineBoxFragment::Create(this); + + return base::AdoptRef(new NGLayoutResult(std::move(fragment), this)); } } // namespace blink 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 39699703599..4e2cd3e3185 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 @@ -6,10 +6,13 @@ #define NGLineBoxFragmentBuilder_h #include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.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" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" +#include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h" #include "third_party/blink/renderer/platform/wtf/allocator.h" namespace blink { @@ -24,26 +27,40 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final STACK_ALLOCATED(); public: - NGLineBoxFragmentBuilder(NGInlineNode, - scoped_refptr<const ComputedStyle>, - WritingMode, - TextDirection); - ~NGLineBoxFragmentBuilder() override; + NGLineBoxFragmentBuilder(NGInlineNode node, + scoped_refptr<const ComputedStyle> style, + WritingMode writing_mode, + TextDirection) + : NGContainerFragmentBuilder(style, writing_mode, TextDirection::kLtr), + node_(node), + line_box_type_(NGPhysicalLineBoxFragment::kNormalLineBox), + base_direction_(TextDirection::kLtr) {} void Reset(); - LayoutUnit LineHeight() const; + LayoutUnit LineHeight() const { + return metrics_.LineHeight().ClampNegativeToZero(); + } + + // Mark this line box is an "empty" line box. See NGLineBoxType. + void SetIsEmptyLineBox(); const NGLineHeightMetrics& Metrics() const { return metrics_; } - void SetMetrics(const NGLineHeightMetrics&); + void SetMetrics(const NGLineHeightMetrics& metrics) { metrics_ = metrics; } - void SetBaseDirection(TextDirection); + void SetBaseDirection(TextDirection direction) { + base_direction_ = direction; + } - void SwapPositionedFloats(Vector<NGPositionedFloat>*); + void SwapPositionedFloats(Vector<NGPositionedFloat>* positioned_floats) { + positioned_floats_.swap(*positioned_floats); + } // Set the break token for the fragment to build. // A finished break token will be attached if not set. - void SetBreakToken(scoped_refptr<NGInlineBreakToken>); + void SetBreakToken(scoped_refptr<NGInlineBreakToken> break_token) { + break_token_ = std::move(break_token); + } // A data struct to keep NGLayoutResult or fragment until the box tree // structures and child offsets are finalized. @@ -54,8 +71,14 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final scoped_refptr<const NGPhysicalFragment> fragment; LayoutObject* out_of_flow_positioned_box = nullptr; LayoutObject* out_of_flow_containing_box = nullptr; + // The offset of the border box, initially in this child coordinate system. + // |ComputeInlinePositions()| converts it to the offset within the line box. NGLogicalOffset offset; + // The inline size of the margin box. LayoutUnit inline_size; + LayoutUnit margin_line_left; + // The index of |box_data_list_|, used in |PrepareForReorder()| and + // |UpdateAfterReorder()| to track children of boxes across BiDi reorder. unsigned box_data_index = 0; UBiDiLevel bidi_level = 0xff; @@ -108,7 +131,9 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final } bool HasBidiLevel() const { return bidi_level != 0xff; } bool IsPlaceholder() const { return !HasFragment() && !HasBidiLevel(); } - const NGPhysicalFragment* PhysicalFragment() const; + const NGPhysicalFragment* PhysicalFragment() const { + return layout_result ? layout_result->PhysicalFragment() : fragment.get(); + } }; // A vector of Child. @@ -123,15 +148,16 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final children_ = std::move(other.children_); } - Child& operator[](unsigned i) { return children_[i]; } + Child& operator[](wtf_size_t i) { return children_[i]; } + const Child& operator[](wtf_size_t i) const { return children_[i]; } - unsigned size() const { return children_.size(); } + wtf_size_t size() const { return children_.size(); } bool IsEmpty() const { return children_.IsEmpty(); } void ReserveInitialCapacity(unsigned capacity) { children_.ReserveInitialCapacity(capacity); } - void clear() { children_.clear(); } - void resize(size_t size) { children_.resize(size); } + void clear() { children_.resize(0); } + void resize(wtf_size_t size) { children_.resize(size); } using iterator = Vector<Child, 16>::iterator; iterator begin() { return children_.begin(); } @@ -154,11 +180,14 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final void AddChild(Args&&... args) { children_.emplace_back(std::forward<Args>(args)...); } - void InsertChild(unsigned, - scoped_refptr<NGLayoutResult>, - const NGLogicalOffset&, + void InsertChild(unsigned index, + scoped_refptr<NGLayoutResult> layout_result, + const NGLogicalOffset& offset, LayoutUnit inline_size, - UBiDiLevel); + UBiDiLevel bidi_level) { + children_.insert(index, Child{std::move(layout_result), offset, + inline_size, bidi_level}); + } void MoveInInlineDirection(LayoutUnit, unsigned start, unsigned end); void MoveInBlockDirection(LayoutUnit); @@ -180,13 +209,18 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final NGLineHeightMetrics metrics_; Vector<NGPositionedFloat> positioned_floats_; - scoped_refptr<NGInlineBreakToken> break_token_; - + NGPhysicalLineBoxFragment::NGLineBoxType line_box_type_; TextDirection base_direction_; + friend class NGLayoutResult; + friend class NGPhysicalLineBoxFragment; + DISALLOW_COPY_AND_ASSIGN(NGLineBoxFragmentBuilder); }; } // namespace blink +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS( + blink::NGLineBoxFragmentBuilder::Child); + #endif // NGLineBoxFragmentBuilder 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 4ff79b7653b..b35d9baa564 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 @@ -5,6 +5,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/logical_values.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" @@ -17,6 +18,7 @@ #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/shape_result_view.h" #include "third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h" namespace blink { @@ -134,7 +136,7 @@ void NGLineBreaker::ComputeBaseDirection() { const String& text = Text(); if (text.Is8Bit()) return; - size_t end_offset = text.find(kNewlineCharacter, offset_); + wtf_size_t end_offset = text.find(kNewlineCharacter, offset_); base_direction_ = NGBidiParagraph::BaseDirectionForString( end_offset == kNotFound ? StringView(text, offset_) @@ -156,9 +158,20 @@ void NGLineBreaker::PrepareNextLine() { } 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_); + line_info_->SetLineStyle(node_, items_data_, use_first_line_style_); + + DCHECK(!line_info_->TextIndent()); + if (line_info_->LineStyle().ShouldUseTextIndent( + is_first_formatted_line_, previous_line_had_forced_break_)) { + const Length& length = line_info_->LineStyle().TextIndent(); + LayoutUnit maximum_value; + // Ignore percentages (resolve to 0) when calculating min/max intrinsic + // sizes. + if (length.IsPercentOrCalc() && mode_ == NGLineBreakerMode::kContent) + maximum_value = constraint_space_.AvailableSize().inline_size; + line_info_->SetTextIndent(MinimumValueForLength(length, maximum_value)); + } + // 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 @@ -314,9 +327,15 @@ void NGLineBreaker::HandleText(const NGInlineItem& item) { DCHECK(item.TextShapeResult()); // If we're trailing, only trailing spaces can be included in this line. - if (state_ == LineBreakState::kTrailing && - CanBreakAfterLast(*item_results_)) { - return HandleTrailingSpaces(item); + if (state_ == LineBreakState::kTrailing) { + if (CanBreakAfterLast(*item_results_)) + return HandleTrailingSpaces(item); + // When a run of preserved spaces are across items, |CanBreakAfterLast| is + // false for between spaces. But we still need to handle them as trailing + // spaces. + const String& text = Text(); + if (offset_ < text.length() && text[offset_] == kSpaceCharacter) + return HandleTrailingSpaces(item); } // Skip leading collapsible spaces. @@ -402,8 +421,8 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result, // has item-specific info as context. Should they be part of ShapeLine() to // instantiate once, or is this just fine since instatiation is not // expensive? - DCHECK_EQ(item.TextShapeResult()->StartIndexForResult(), item.StartOffset()); - DCHECK_EQ(item.TextShapeResult()->EndIndexForResult(), item.EndOffset()); + DCHECK_EQ(item.TextShapeResult()->StartIndex(), item.StartOffset()); + DCHECK_EQ(item.TextShapeResult()->EndIndex(), item.EndOffset()); RunSegmenter::RunSegmenterRange segment_range = item.CreateRunSegmenterRange(); ShapingLineBreaker breaker(&shaper_, &item.Style()->GetFont(), @@ -424,7 +443,7 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result, if (break_anywhere_if_overflow_ && !override_break_anywhere_) options |= ShapingLineBreaker::kNoResultIfOverflow; ShapingLineBreaker::Result result; - scoped_refptr<const ShapeResult> shape_result = breaker.ShapeLine( + scoped_refptr<const ShapeResultView> 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 @@ -447,7 +466,9 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result, item_result->inline_size = shape_result->SnappedWidth().ClampNegativeToZero(); item_result->end_offset = result.break_offset; item_result->shape_result = std::move(shape_result); - DCHECK_GT(item_result->end_offset, item_result->start_offset); + // It is critical to move offset forward, or NGLineBreaker may keep adding + // NGInlineItemResult until all the memory is consumed. + CHECK_GT(item_result->end_offset, item_result->start_offset) << Text(); // * If width <= available_width: // * If offset < item.EndOffset(): the break opportunity to fit is found. @@ -482,33 +503,37 @@ scoped_refptr<ShapeResult> NGLineBreaker::ShapeText(const NGInlineItem& item, // 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( +scoped_refptr<ShapeResultView> 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; + // TODO(layout-dev): Add support for subsetting a ShapeResultView thereby + // avoiding extra copy to create a new full ShapeResult here. + auto source_result = item_result.shape_result->CreateShapeResult(); 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()); + DCHECK_GE(start_offset, source_result->StartIndex()); + DCHECK_LE(end_offset, source_result->EndIndex()); + DCHECK(start_offset > source_result->StartIndex() || + end_offset < source_result->EndIndex()); + + ShapeResultView::Segment segments[2]; + unsigned count = 0; - scoped_refptr<ShapeResult> new_result; - unsigned last_safe = source_result.PreviousSafeToBreakOffset(end_offset); + 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); + segments[count++] = {source_result.get(), start_offset, last_safe}; + + scoped_refptr<ShapeResult> end_result; 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); + end_result = ShapeText(*item_result.item, std::max(last_safe, start_offset), + end_offset); + segments[count++] = {end_result.get(), 0, end_offset}; + DCHECK_EQ(end_result->Direction(), source_result->Direction()); } - DCHECK(new_result); - return new_result; + DCHECK_GE(count, 1u); + return ShapeResultView::Create(&segments[0], count); } // Update |ShapeResult| in |item_result| to match to its |start_offset| and @@ -556,7 +581,8 @@ void NGLineBreaker::HandleTrailingSpaces(const NGInlineItem& item) { NGInlineItemResult* item_result = AddItem(item, end); item_result->has_only_trailing_spaces = true; - item_result->shape_result = item.TextShapeResult(); + item_result->shape_result = + ShapeResultView::Create(item.TextShapeResult().get()); if (item_result->start_offset == item.StartOffset() && item_result->end_offset == item.EndOffset()) item_result->inline_size = item_result->shape_result->SnappedWidth(); @@ -592,11 +618,11 @@ void NGLineBreaker::RemoveTrailingCollapsibleSpace() { // 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 = + if (scoped_refptr<const ShapeResultView>& 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->shape_result = collapsed_shape_result; item_result->inline_size = item_result->shape_result->SnappedWidth(); position_ += item_result->inline_size; } else { @@ -617,7 +643,7 @@ LayoutUnit NGLineBreaker::TrailingCollapsibleSpaceWidth() { // 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 = + if (scoped_refptr<const ShapeResultView>& collapsed_shape_result = trailing_collapsible_space_->collapsed_shape_result) { return item_result->inline_size - collapsed_shape_result->SnappedWidth(); } @@ -671,7 +697,8 @@ void NGLineBreaker::AppendHyphen(const NGInlineItem& item) { 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)); + /* is_ellipsis_style */ false, + ShapeResultView::Create(hyphen_result.get())); SetLineEndFragment(builder.ToTextFragment()); } @@ -769,7 +796,7 @@ void NGLineBreaker::HandleAtomicInline(const NGInlineItem& item) { if (mode_ == NGLineBreakerMode::kContent) { item_result->layout_result = NGBlockNode(ToLayoutBox(item.GetLayoutObject())) - .LayoutAtomicInline(constraint_space_, + .LayoutAtomicInline(constraint_space_, node_.Style(), line_info_->LineStyle().GetFontBaseline(), line_info_->UseFirstLineStyle()); DCHECK(item_result->layout_result->PhysicalFragment()); @@ -779,11 +806,10 @@ void NGLineBreaker::HandleAtomicInline(const NGInlineItem& item) { *item_result->layout_result->PhysicalFragment()) .InlineSize(); } else { - NGBlockNode block_node(ToLayoutBox(item.GetLayoutObject())); + NGBlockNode child(ToLayoutBox(item.GetLayoutObject())); MinMaxSizeInput input; - MinMaxSize sizes = ComputeMinAndMaxContentContribution( - constraint_space_.GetWritingMode(), block_node, input, - &constraint_space_); + MinMaxSize sizes = + ComputeMinAndMaxContentContribution(node_.Style(), child, input); item_result->inline_size = mode_ == NGLineBreakerMode::kMinContent ? sizes.min_size : sizes.max_size; @@ -846,13 +872,13 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item) { // to the unpositioned floats list and abort. if (mode_ != NGLineBreakerMode::kContent) { AddUnpositionedFloat(unpositioned_floats_, container_builder_, - std::move(unpositioned_float)); + std::move(unpositioned_float), constraint_space_); return; } LayoutUnit inline_margin_size = - ComputeMarginBoxInlineSizeForUnpositionedFloat(constraint_space_, - &unpositioned_float); + ComputeMarginBoxInlineSizeForUnpositionedFloat( + constraint_space_, node_.Style(), &unpositioned_float); LayoutUnit bfc_block_offset = line_opportunity_.bfc_block_offset; @@ -878,21 +904,23 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item) { bool float_after_line = !can_fit_float || exclusion_space_->LastFloatBlockStart() > bfc_block_offset || - exclusion_space_->ClearanceOffset(float_style.Clear()) > bfc_block_offset; + exclusion_space_->ClearanceOffset( + ResolvedClear(float_style.Clear(), constraint_space_.Direction())) > + bfc_block_offset; // Check if we already have a pending float. That's because a float cannot be // higher than any block or floated box generated before. if (!unpositioned_floats_->IsEmpty() || float_after_line) { AddUnpositionedFloat(unpositioned_floats_, container_builder_, - std::move(unpositioned_float)); + std::move(unpositioned_float), constraint_space_); } else { NGPositionedFloat positioned_float = PositionFloat( constraint_space_.AvailableSize(), constraint_space_.PercentageResolutionSize(), constraint_space_.ReplacedPercentageResolutionSize(), {constraint_space_.BfcOffset().line_offset, bfc_block_offset}, - constraint_space_.BfcOffset().block_offset, &unpositioned_float, - constraint_space_, exclusion_space_); + &unpositioned_float, constraint_space_, node_.Style(), + exclusion_space_); positioned_floats_->push_back(positioned_float); NGLayoutOpportunity opportunity = exclusion_space_->FindLayoutOpportunity( @@ -943,9 +971,7 @@ void NGLineBreaker::HandleOpenTag(const NGInlineItem& item) { // 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 (!item_result->should_create_line_box && - (item_result->inline_size || - (item_result->margins.inline_start && !in_line_height_quirks_mode_))) + if (!item_result->should_create_line_box && !item.IsEmptyItem()) item_result->should_create_line_box = true; } @@ -968,9 +994,7 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item) { margins.inline_end + borders.inline_end + paddings.inline_end; position_ += item_result->inline_size; - if (!item_result->should_create_line_box && - (item_result->inline_size || - (margins.inline_end && !in_line_height_quirks_mode_))) + if (!item_result->should_create_line_box && !item.IsEmptyItem()) item_result->should_create_line_box = true; } DCHECK(item.GetLayoutObject() && item.GetLayoutObject()->Parent()); 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 9ccd74c3bd8..987037c1666 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 @@ -98,7 +98,7 @@ class CORE_EXPORT NGLineBreaker { const NGInlineItem&, LayoutUnit available_width); - scoped_refptr<ShapeResult> TruncateLineEndResult( + scoped_refptr<ShapeResultView> TruncateLineEndResult( const NGInlineItemResult& item_result, unsigned end_offset); void UpdateShapeResult(NGInlineItemResult*); @@ -212,7 +212,7 @@ class CORE_EXPORT NGLineBreaker { // multiple times. struct TrailingCollapsibleSpace { NGInlineItemResult* item_result; - scoped_refptr<const ShapeResult> collapsed_shape_result; + scoped_refptr<const ShapeResultView> collapsed_shape_result; }; base::Optional<TrailingCollapsibleSpace> trailing_collapsible_space_; 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 6522f958568..53f11d1ac57 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 @@ -8,10 +8,11 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h" #include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" -#include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.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/platform/fonts/shaping/shape_result_view.h" #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" namespace blink { @@ -35,10 +36,10 @@ class NGLineBreakerTest : public NGBaseLayoutAlgorithmTest { NGConstraintSpace space = NGConstraintSpaceBuilder( - WritingMode::kHorizontalTb, - /* icb_size */ {NGSizeIndefinite, NGSizeIndefinite}) + WritingMode::kHorizontalTb, WritingMode::kHorizontalTb, + /* is_new_fc */ false) .SetAvailableSize({available_width, NGSizeIndefinite}) - .ToConstraintSpace(WritingMode::kHorizontalTb); + .ToConstraintSpace(); Vector<NGPositionedFloat> positioned_floats; NGUnpositionedFloatVector unpositioned_floats; @@ -209,6 +210,29 @@ TEST_F(NGLineBreakerTest, OverflowMargin) { EXPECT_EQ("789", ToString(lines[2], node)); } +TEST_F(NGLineBreakerTest, OverflowAfterSpacesAcrossElements) { + LoadAhem(); + NGInlineNode node = CreateInlineNode(R"HTML( + <!DOCTYPE html> + <style> + div { + font: 10px/1 Ahem; + white-space: pre-wrap; + width: 10ch; + word-wrap: break-word; + } + </style> + <div id=container><span>12345 </span> 1234567890123</div> + )HTML"); + + Vector<NGInlineItemResults> lines; + lines = BreakLines(node, LayoutUnit(100)); + EXPECT_EQ(3u, lines.size()); + EXPECT_EQ("12345 ", ToString(lines[0], node)); + EXPECT_EQ("1234567890", ToString(lines[1], node)); + EXPECT_EQ("123", ToString(lines[2], node)); +} + // Tests when the last word in a node wraps, and another node continues. TEST_F(NGLineBreakerTest, WrapLastWord) { LoadAhem(); 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 7289666f0c5..d8d9eba3f0f 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 @@ -6,7 +6,7 @@ #define NGLineHeightMetrics_h #include "third_party/blink/renderer/platform/fonts/font_baseline.h" -#include "third_party/blink/renderer/platform/layout_unit.h" +#include "third_party/blink/renderer/platform/geometry/layout_unit.h" namespace blink { 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 49df71cfc51..2301f6c98a2 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 @@ -6,8 +6,10 @@ #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/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/platform/fonts/font_baseline.h" #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h" +#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" namespace blink { @@ -55,8 +57,8 @@ LayoutUnit NGLineTruncator::TruncateLine( ? String(&kHorizontalEllipsisCharacter, 1) : String(u"..."); HarfBuzzShaper shaper(ellipsis_text); - scoped_refptr<ShapeResult> ellipsis_shape_result = - shaper.Shape(&font, line_direction_); + scoped_refptr<ShapeResultView> ellipsis_shape_result = + ShapeResultView::Create(shaper.Shape(&font, line_direction_).get()); LayoutUnit ellipsis_width = ellipsis_shape_result->SnappedWidth(); // Loop children from the logical last to the logical first to determine where @@ -115,6 +117,24 @@ LayoutUnit NGLineTruncator::TruncateLine( // Hide this child from being painted. void NGLineTruncator::HideChild(NGLineBoxFragmentBuilder::Child* child) { DCHECK(child->HasInFlowFragment()); + + // If this child has self painting layer, not producing fragments will not + // suppress painting because layers are painted separately. Move it out of the + // clipping area. + const NGPhysicalFragment* fragment = child->PhysicalFragment(); + DCHECK(fragment); + if (const NGPhysicalBoxFragment* box_fragment = + ToNGPhysicalBoxFragmentOrNull(fragment)) { + if (box_fragment->HasSelfPaintingLayer()) { + // |avilable_width_| may not be enough when the contaning block has + // paddings, because clipping is at the content box but ellipsizing is at + // the padding box. Just move to the max because we don't know paddings, + // and max should do what we need. + child->offset.inline_offset = LayoutUnit::NearlyMax(); + return; + } + } + // TODO(kojii): Not producing fragments is the most clean and efficient way to // hide them, but we may want to revisit how to do this to reduce special // casing in other code. @@ -185,7 +205,10 @@ bool NGLineTruncator::TruncateChild(LayoutUnit space_for_child, if (!child->fragment) return is_first_child; auto& fragment = ToNGPhysicalTextFragment(*child->fragment); - const ShapeResult* shape_result = fragment.TextShapeResult(); + // TODO(layout-dev): Add support for OffsetToFit to ShapeResultView to avoid + // this copy. + scoped_refptr<blink::ShapeResult> shape_result = + fragment.TextShapeResult()->CreateShapeResult(); if (!shape_result) return is_first_child; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc index 95d3b67e193..de77686df59 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc @@ -54,7 +54,7 @@ std::pair<const Node&, unsigned> ToNodeOffsetPair(const Position& position) { } // namespace -const LayoutBlockFlow* NGInlineFormattingContextOf(const Position& position) { +LayoutBlockFlow* NGInlineFormattingContextOf(const Position& position) { if (!RuntimeEnabledFeatures::LayoutNGEnabled()) return nullptr; if (!NGOffsetMapping::AcceptsPosition(position)) @@ -62,14 +62,7 @@ const LayoutBlockFlow* NGInlineFormattingContextOf(const Position& position) { const auto node_offset_pair = ToNodeOffsetPair(position); const LayoutObject* layout_object = AssociatedLayoutObjectOf(node_offset_pair.first, node_offset_pair.second); - // For an atomic inline, EnclosingNGBlockFlow() may return itself. Example: - // <div><span style='display: inline-block'>foo</span></div> - // EnclosingNGBlockFlow() on SPAN returns SPAN itself. However, the inline - // formatting context of SPAN@Before/After is DIV, not SPAN. - // Therefore, we return its parent's EnclosingNGBlockFlow() instead. - if (layout_object->IsAtomicInlineLevel()) - layout_object = layout_object->Parent(); - return layout_object->EnclosingNGBlockFlow(); + return layout_object->ContainingNGBlockFlow(); } NGOffsetMappingUnit::NGOffsetMappingUnit(NGOffsetMappingUnitType type, @@ -174,7 +167,7 @@ const NGOffsetMapping* NGOffsetMapping::GetFor(const Position& position) { return nullptr; if (!NGOffsetMapping::AcceptsPosition(position)) return nullptr; - return GetFor(NGInlineFormattingContextOf(position)); + return GetForContainingBlockFlow(NGInlineFormattingContextOf(position)); } // static @@ -184,7 +177,12 @@ const NGOffsetMapping* NGOffsetMapping::GetFor( return nullptr; if (!layout_object) return nullptr; - LayoutBlockFlow* block_flow = layout_object->EnclosingNGBlockFlow(); + return GetForContainingBlockFlow(layout_object->ContainingNGBlockFlow()); +} + +// static +const NGOffsetMapping* NGOffsetMapping::GetForContainingBlockFlow( + LayoutBlockFlow* block_flow) { if (!block_flow || !block_flow->ChildrenInline()) return nullptr; NGBlockNode block_node = NGBlockNode(block_flow); @@ -271,6 +269,16 @@ NGMappingUnitRange NGOffsetMapping::GetMappingUnitsForDOMRange( return {result_begin, result_end}; } +NGMappingUnitRange NGOffsetMapping::GetMappingUnitsForNode( + const Node& node) const { + const auto it = ranges_.find(&node); + if (it == ranges_.end()) { + NOTREACHED() << node; + return NGMappingUnitRange(); + } + return {units_.begin() + it->value.first, units_.begin() + it->value.second}; +} + NGMappingUnitRange NGOffsetMapping::GetMappingUnitsForTextContentOffsetRange( unsigned start, unsigned end) const { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h index 195b88cc584..fcea9c0caba 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h @@ -130,6 +130,10 @@ class CORE_EXPORT NGOffsetMapping { // a LayoutObject at hand. static const NGOffsetMapping* GetFor(const LayoutObject*); + // Returns the mapping object of the inline formatting context the given + // LayoutBlockFlow has. + static const NGOffsetMapping* GetForContainingBlockFlow(LayoutBlockFlow*); + // Returns the NGOffsetMappingUnit whose DOM range contains the position. // If there are multiple qualifying units, returns the last one. const NGOffsetMappingUnit* GetMappingUnitForPosition(const Position&) const; @@ -139,6 +143,10 @@ class CORE_EXPORT NGOffsetMapping { // only accepts ranges whose start and end have the same anchor node. NGMappingUnitRange GetMappingUnitsForDOMRange(const EphemeralRange&) const; + // Returns all NGOffsetMappingUnits associated to |node|. Note: |node| should + // have associated mapping. + NGMappingUnitRange GetMappingUnitsForNode(const Node& node) const; + // Returns the text content offset corresponding to the given position. // Returns nullopt when the position is not laid out in this context. base::Optional<unsigned> GetTextContentOffset(const Position&) const; @@ -206,7 +214,7 @@ class CORE_EXPORT NGOffsetMapping { DISALLOW_COPY_AND_ASSIGN(NGOffsetMapping); }; -CORE_EXPORT const LayoutBlockFlow* NGInlineFormattingContextOf(const Position&); +CORE_EXPORT LayoutBlockFlow* NGInlineFormattingContextOf(const Position&); } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc index 50b791d9a08..2705012b3d4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc @@ -1149,4 +1149,40 @@ TEST_F(NGOffsetMappingTest, TextOverflowEllipsis) { TEST_RANGE(mapping.GetRanges(), text, 0u, 1u); } +// Test |GetOffsetMapping| which is available both for LayoutNG and for legacy. +class NGOffsetMappingGetterTest : public RenderingTest, + public testing::WithParamInterface<bool>, + private ScopedLayoutNGForTest { + public: + NGOffsetMappingGetterTest() : ScopedLayoutNGForTest(GetParam()) {} +}; + +INSTANTIATE_TEST_CASE_P(NGOffsetMappingTest, + NGOffsetMappingGetterTest, + testing::Bool()); + +TEST_P(NGOffsetMappingGetterTest, Get) { + SetBodyInnerHTML(R"HTML( + <div id=container> + Whitespaces in this text should be collapsed. + </div> + )HTML"); + LayoutBlockFlow* layout_block_flow = + ToLayoutBlockFlow(GetLayoutObjectByElementId("container")); + DCHECK(layout_block_flow->ChildrenInline()); + + // For the purpose of this test, ensure this is laid out by each layout + // engine. + DCHECK_EQ(layout_block_flow->IsLayoutNGMixin(), GetParam()); + + std::unique_ptr<NGOffsetMapping> storage; + const NGOffsetMapping* mapping = + NGInlineNode::GetOffsetMapping(layout_block_flow, &storage); + EXPECT_TRUE(mapping); + EXPECT_EQ(!storage, GetParam()); // |storage| is used only for legacy. + + const String& text_content = mapping->GetText(); + EXPECT_EQ(text_content, "Whitespaces in this text should be collapsed."); +} + } // namespace blink 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 9564e068cb3..751766a4c72 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,29 +5,50 @@ #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_line_box_fragment_builder.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 { + +struct SameSizeAsNGPhysicalLineBoxFragment : NGPhysicalContainerFragment { + void* pointer; + NGLineHeightMetrics metrics; +}; + +static_assert(sizeof(NGPhysicalLineBoxFragment) == + sizeof(SameSizeAsNGPhysicalLineBoxFragment), + "NGPhysicalLineBoxFragment should stay small"); + +} // namespace + +scoped_refptr<const NGPhysicalLineBoxFragment> +NGPhysicalLineBoxFragment::Create(NGLineBoxFragmentBuilder* builder) { + // We store the children list inline in the fragment as a flexible + // array. Therefore, we need to make sure to allocate enough space for + // that array here, which requires a manual allocation + placement new. + // The initialization of the array is done by NGPhysicalContainerFragment; + // we pass the buffer as a constructor argument. + void* data = ::WTF::Partitions::FastMalloc( + sizeof(NGPhysicalLineBoxFragment) + + builder->children_.size() * sizeof(NGLinkStorage), + ::WTF::GetStringWithTypeName<NGPhysicalLineBoxFragment>()); + new (data) NGPhysicalLineBoxFragment(builder); + return base::AdoptRef(static_cast<NGPhysicalLineBoxFragment*>(data)); +} + NGPhysicalLineBoxFragment::NGPhysicalLineBoxFragment( - const ComputedStyle& style, - NGStyleVariant style_variant, - NGPhysicalSize size, - Vector<NGLink>& children, - const NGLineHeightMetrics& metrics, - TextDirection base_direction, - scoped_refptr<NGBreakToken> break_token) - : NGPhysicalContainerFragment(nullptr, - style, - style_variant, - size, + NGLineBoxFragmentBuilder* builder) + : NGPhysicalContainerFragment(builder, + builder->GetWritingMode(), + children_, kFragmentLineBox, - 0, - children, - std::move(break_token)), - metrics_(metrics) { - base_direction_ = static_cast<unsigned>(base_direction); + builder->line_box_type_), + metrics_(builder->metrics_) { + style_ = std::move(builder->style_); + base_direction_ = static_cast<unsigned>(builder->base_direction_); } NGLineHeightMetrics NGPhysicalLineBoxFragment::BaselineMetrics( 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 3fa31d418a4..57a4a6876a4 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 @@ -12,18 +12,39 @@ namespace blink { +class NGLineBoxFragmentBuilder; + class CORE_EXPORT NGPhysicalLineBoxFragment final : public NGPhysicalContainerFragment { public: - // This modifies the passed-in children vector. - NGPhysicalLineBoxFragment(const ComputedStyle&, - NGStyleVariant style_variant, - NGPhysicalSize size, - Vector<NGLink>& children, - const NGLineHeightMetrics&, - TextDirection base_direction, - scoped_refptr<NGBreakToken> break_token = nullptr); + enum NGLineBoxType { + kNormalLineBox, + // This is an "empty" line box, or "certain zero-height line boxes": + // https://drafts.csswg.org/css2/visuren.html#phantom-line-box + // that are ignored for margin collapsing and for other purposes. + // https://drafts.csswg.org/css2/box.html#collapsing-margins + // Also see |NGInlineItem::IsEmptyItem|. + kEmptyLineBox + }; + + static scoped_refptr<const NGPhysicalLineBoxFragment> Create( + NGLineBoxFragmentBuilder* builder); + + ~NGPhysicalLineBoxFragment() { + for (const NGLinkStorage& child : Children()) + child.fragment->Release(); + } + + NGLineBoxType LineBoxType() const { + return static_cast<NGLineBoxType>(sub_type_); + } + bool IsEmptyLineBox() const { return LineBoxType() == kEmptyLineBox; } + ChildLinkList Children() const final { + return ChildLinkList(num_children_, &children_[0]); + } + + const ComputedStyle& Style() const { return *style_; } const NGLineHeightMetrics& Metrics() const { return metrics_; } // The base direction of this line. Also known as the paragraph direction. @@ -59,7 +80,11 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final bool HasSoftWrapToNextLine() const; private: + NGPhysicalLineBoxFragment(NGLineBoxFragmentBuilder* builder); + + scoped_refptr<const ComputedStyle> style_; NGLineHeightMetrics metrics_; + NGLinkStorage children_[]; }; DEFINE_TYPE_CASTS(NGPhysicalLineBoxFragment, 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 7644d245120..183f690f962 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 @@ -10,12 +10,23 @@ #include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h" #include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" namespace blink { namespace { +struct SameSizeAsNGPhysicalTextFragment : NGPhysicalFragment { + void* pointers[3]; + unsigned offsets[2]; +}; + +static_assert(sizeof(NGPhysicalTextFragment) == + sizeof(SameSizeAsNGPhysicalTextFragment), + "NGPhysicalTextFragment should stay small"); + inline bool IsPhysicalTextFragmentAnonymousText( const LayoutObject* layout_object) { if (!layout_object) @@ -26,35 +37,86 @@ inline bool IsPhysicalTextFragmentAnonymousText( return !node || node->IsPseudoElement(); } +NGLineOrientation ToLineOrientation(WritingMode writing_mode) { + switch (writing_mode) { + case WritingMode::kHorizontalTb: + return NGLineOrientation::kHorizontal; + case WritingMode::kVerticalRl: + case WritingMode::kVerticalLr: + case WritingMode::kSidewaysRl: + return NGLineOrientation::kClockWiseVertical; + case WritingMode::kSidewaysLr: + return NGLineOrientation::kCounterClockWiseVertical; + } + NOTREACHED(); + return NGLineOrientation::kHorizontal; +} + } // anonymous namespace NGPhysicalTextFragment::NGPhysicalTextFragment( - LayoutObject* layout_object, - const ComputedStyle& style, - NGStyleVariant style_variant, - NGTextType text_type, - const String& text, + const NGPhysicalTextFragment& source, unsigned start_offset, unsigned end_offset, - NGPhysicalSize size, - NGLineOrientation line_orientation, - NGTextEndEffect end_effect, - scoped_refptr<const ShapeResult> shape_result) - : NGPhysicalFragment(layout_object, - style, - style_variant, - size, + scoped_refptr<const ShapeResultView> shape_result) + : NGPhysicalFragment(source.GetLayoutObject(), + source.StyleVariant(), + source.IsHorizontal() + ? NGPhysicalSize{shape_result->SnappedWidth(), + source.Size().height} + : NGPhysicalSize{source.Size().width, + shape_result->SnappedWidth()}, kFragmentText, - text_type), - text_(text), + source.TextType()), + text_(source.text_), start_offset_(start_offset), end_offset_(end_offset), - shape_result_(shape_result), - line_orientation_(static_cast<unsigned>(line_orientation)), - end_effect_(static_cast<unsigned>(end_effect)), - is_anonymous_text_(IsPhysicalTextFragmentAnonymousText(layout_object)) { + shape_result_(shape_result) { + DCHECK_GE(start_offset_, source.StartOffset()); + DCHECK_LE(end_offset_, source.EndOffset()); DCHECK(shape_result_ || IsFlowControl()) << ToString(); - self_ink_overflow_ = ComputeSelfInkOverflow(); + DCHECK(!source.rare_data_ || !source.rare_data_->style_); + line_orientation_ = source.line_orientation_; + is_anonymous_text_ = source.is_anonymous_text_; + + UpdateSelfInkOverflow(); +} + +NGPhysicalTextFragment::NGPhysicalTextFragment(NGTextFragmentBuilder* builder) + : NGPhysicalFragment(builder, kFragmentText, builder->text_type_), + text_(builder->text_), + start_offset_(builder->start_offset_), + end_offset_(builder->end_offset_), + shape_result_(std::move(builder->shape_result_)) { + DCHECK(shape_result_ || IsFlowControl()) << ToString(); + line_orientation_ = + static_cast<unsigned>(ToLineOrientation(builder->GetWritingMode())); + is_anonymous_text_ = + IsPhysicalTextFragmentAnonymousText(builder->layout_object_); + + if (UNLIKELY(StyleVariant() == NGStyleVariant::kEllipsis)) + EnsureRareData()->style_ = std::move(builder->style_); + + UpdateSelfInkOverflow(); +} + +NGPhysicalTextFragment::RareData* NGPhysicalTextFragment::EnsureRareData() { + if (!rare_data_) + rare_data_ = std::make_unique<RareData>(); + return rare_data_.get(); +} + +const ComputedStyle& NGPhysicalTextFragment::Style() const { + switch (StyleVariant()) { + case NGStyleVariant::kStandard: + case NGStyleVariant::kFirstLine: + return NGPhysicalFragment::Style(); + case NGStyleVariant::kEllipsis: + DCHECK(rare_data_ && rare_data_->style_); + return *rare_data_->style_; + } + NOTREACHED(); + return NGPhysicalFragment::Style(); } // Convert logical cooridnate to local physical coordinate. @@ -85,8 +147,11 @@ LayoutUnit NGPhysicalTextFragment::InlinePositionForOffset( offset -= start_offset_; if (shape_result_) { - return round(shape_result_->CaretPositionForOffset(offset, Text(), - adjust_mid_cluster)); + // TODO(layout-dev): Move caret position out of ShapeResult and into a + // separate support class that can take a ShapeResult or ShapeResultView. + // Allows for better code separation and avoids the extra copy below. + return round(shape_result_->CreateShapeResult()->CaretPositionForOffset( + offset, Text(), adjust_mid_cluster)); } // This fragment is a flow control because otherwise ShapeResult exists. @@ -141,9 +206,20 @@ NGPhysicalOffsetRect NGPhysicalTextFragment::LocalRect( return {}; } -NGPhysicalOffsetRect NGPhysicalTextFragment::ComputeSelfInkOverflow() const { - if (UNLIKELY(!shape_result_)) - return LocalRect(); +NGPhysicalOffsetRect NGPhysicalTextFragment::SelfInkOverflow() const { + return UNLIKELY(rare_data_) ? rare_data_->self_ink_overflow_ : LocalRect(); +} + +void NGPhysicalTextFragment::ClearSelfInkOverflow() { + if (UNLIKELY(rare_data_)) + rare_data_->self_ink_overflow_ = LocalRect(); +} + +void NGPhysicalTextFragment::UpdateSelfInkOverflow() { + if (UNLIKELY(!shape_result_)) { + ClearSelfInkOverflow(); + return; + } // Glyph bounds is in logical coordinate, origin at the alphabetic baseline. LayoutRect ink_overflow = EnclosingLayoutRect(shape_result_->Bounds()); @@ -190,8 +266,13 @@ NGPhysicalOffsetRect NGPhysicalTextFragment::ComputeSelfInkOverflow() const { // 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_ink_overflow = ConvertToLocal(ink_overflow); - local_ink_overflow.Unite(LocalRect()); - return local_ink_overflow; + NGPhysicalOffsetRect local_rect = LocalRect(); + if (local_rect.Contains(local_ink_overflow)) { + ClearSelfInkOverflow(); + return; + } + local_ink_overflow.Unite(local_rect); + EnsureRareData()->self_ink_overflow_ = local_ink_overflow; } scoped_refptr<const NGPhysicalFragment> NGPhysicalTextFragment::TrimText( @@ -201,15 +282,14 @@ scoped_refptr<const NGPhysicalFragment> NGPhysicalTextFragment::TrimText( DCHECK_GE(new_start_offset, StartOffset()); DCHECK_GT(new_end_offset, new_start_offset); DCHECK_LE(new_end_offset, EndOffset()); + // TODO(layout-dev): Add sub-range version of CreateShapeResult to avoid + // this double copy. scoped_refptr<ShapeResult> new_shape_result = - shape_result_->SubRange(new_start_offset, new_end_offset); - LayoutUnit new_inline_size = new_shape_result->SnappedWidth(); + shape_result_->CreateShapeResult()->SubRange(new_start_offset, + new_end_offset); return base::AdoptRef(new NGPhysicalTextFragment( - layout_object_, Style(), static_cast<NGStyleVariant>(style_variant_), - TextType(), text_, new_start_offset, new_end_offset, - IsHorizontal() ? NGPhysicalSize{new_inline_size, size_.height} - : NGPhysicalSize{size_.width, new_inline_size}, - LineOrientation(), EndEffect(), std::move(new_shape_result))); + *this, new_start_offset, new_end_offset, + ShapeResultView::Create(new_shape_result.get()))); } unsigned NGPhysicalTextFragment::TextOffsetForPoint( @@ -217,8 +297,10 @@ unsigned NGPhysicalTextFragment::TextOffsetForPoint( const ComputedStyle& style = Style(); const LayoutUnit& point_in_line_direction = style.IsHorizontalWritingMode() ? point.left : point.top; - if (const ShapeResult* shape_result = TextShapeResult()) { - return shape_result->CaretOffsetForHitTest( + if (const ShapeResultView* shape_result = TextShapeResult()) { + // TODO(layout-dev): Move caret logic out of ShapeResult into separate + // support class for code health and to avoid this copy. + return shape_result->CreateShapeResult()->CaretOffsetForHitTest( point_in_line_direction.ToFloat(), Text(), BreakGlyphs) + StartOffset(); } @@ -258,7 +340,7 @@ UBiDiLevel NGPhysicalTextFragment::BidiLevel() const { TextDirection NGPhysicalTextFragment::ResolvedDirection() const { if (TextShapeResult()) return TextShapeResult()->Direction(); - return NGPhysicalFragment::ResolvedDirection(); + return DirectionFromLevel(BidiLevel()); } } // namespace blink 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 52d6924555c..b9111472f07 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 @@ -10,12 +10,14 @@ #include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" #include "third_party/blink/renderer/platform/fonts/ng_text_fragment_paint_info.h" #include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h" +#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" #include "third_party/blink/renderer/platform/wtf/text/string_view.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" namespace blink { struct NGPhysicalOffsetRect; +class NGTextFragmentBuilder; enum class AdjustMidCluster; @@ -57,17 +59,7 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { // enough to store. }; - NGPhysicalTextFragment(LayoutObject* layout_object, - const ComputedStyle& style, - NGStyleVariant style_variant, - NGTextType text_type, - const String& text, - unsigned start_offset, - unsigned end_offset, - NGPhysicalSize size, - NGLineOrientation line_orientation, - NGTextEndEffect end_effect, - scoped_refptr<const ShapeResult> shape_result); + NGPhysicalTextFragment(NGTextFragmentBuilder*); NGTextType TextType() const { return static_cast<NGTextType>(sub_type_); } // True if this is a generated text. @@ -80,12 +72,14 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { return IsLineBreak() || TextType() == kFlowControl; } + const ComputedStyle& Style() const; + unsigned Length() const { return end_offset_ - start_offset_; } StringView Text() const { return StringView(text_, start_offset_, Length()); } const String& TextContent() const { return text_; } // ShapeResult may be nullptr if |IsFlowControl()|. - const ShapeResult* TextShapeResult() const { return shape_result_.get(); } + const ShapeResultView* TextShapeResult() const { return shape_result_.get(); } // Start/end offset to the text of the block container. unsigned StartOffset() const { return start_offset_; } @@ -110,11 +104,7 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { // The visual bounding box that includes glpyh bounding box and CSS // properties, in local coordinates. - NGPhysicalOffsetRect SelfInkOverflow() const { return self_ink_overflow_; } - - NGTextEndEffect EndEffect() const { - return static_cast<NGTextEndEffect>(end_effect_); - } + NGPhysicalOffsetRect SelfInkOverflow() const; // Create a new fragment that has part of the text of this fragment. // All other properties are the same as this fragment. @@ -135,8 +125,8 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { // Returns the text offset in the fragment placed closest to the given point. unsigned TextOffsetForPoint(const NGPhysicalOffset&) const; - UBiDiLevel BidiLevel() const override; - TextDirection ResolvedDirection() const override; + UBiDiLevel BidiLevel() const; + TextDirection ResolvedDirection() const; // Compute line-relative coordinates for given offsets, this is not // flow-relative: @@ -146,13 +136,26 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { unsigned end_offset) const; private: + // For use by TrimText only + NGPhysicalTextFragment(const NGPhysicalTextFragment& source, + unsigned start_offset, + unsigned end_offset, + scoped_refptr<const ShapeResultView> shape_result); + + struct RareData { + NGPhysicalOffsetRect self_ink_overflow_; + scoped_refptr<const ComputedStyle> style_; // Used only for ellipsis. + }; + RareData* EnsureRareData(); + LayoutUnit InlinePositionForOffset(unsigned offset, LayoutUnit (*round)(float), AdjustMidCluster) const; NGPhysicalOffsetRect ConvertToLocal(const LayoutRect&) const; - NGPhysicalOffsetRect ComputeSelfInkOverflow() const; + void UpdateSelfInkOverflow(); + void ClearSelfInkOverflow(); // The text of NGInlineNode; i.e., of a parent block. The text for this // fragment is a substring(start_offset_, end_offset_) of this string. @@ -161,12 +164,9 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { // Start and end offset of the parent block text. const unsigned start_offset_; const unsigned end_offset_; - NGPhysicalOffsetRect self_ink_overflow_; - const scoped_refptr<const ShapeResult> shape_result_; + const scoped_refptr<const ShapeResultView> shape_result_; - const unsigned line_orientation_ : 2; // NGLineOrientation - const unsigned end_effect_ : 1; // NGTextEndEffect - const unsigned is_anonymous_text_ : 1; + std::unique_ptr<RareData> rare_data_; }; DEFINE_TYPE_CASTS(NGPhysicalTextFragment, 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 4419bfca2e6..0797df848c4 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 @@ -76,9 +76,10 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRectRTL) { ASSERT_EQ(2u, text_fragments.size()); // The 2nd line starts at 12, because the div has a bidi-control. EXPECT_EQ(12u, text_fragments[1]->StartOffset()); - EXPECT_EQ(NGPhysicalOffsetRect({LayoutUnit(50), LayoutUnit(0)}, - {LayoutUnit(20), LayoutUnit(10)}), - text_fragments[1]->LocalRect(14, 16)); + // TODO(layout-dev): Investigate whether this is correct. + // EXPECT_EQ(NGPhysicalOffsetRect({LayoutUnit(50), LayoutUnit(0)}, + // {LayoutUnit(20), LayoutUnit(10)}), + // text_fragments[1]->LocalRect(14, 16)); } TEST_F(NGPhysicalTextFragmentTest, LocalRectVLR) { 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 64a42f1a618..07ee69f28b1 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 @@ -8,33 +8,10 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" +#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" namespace blink { -namespace { - -NGLineOrientation ToLineOrientation(WritingMode writing_mode) { - switch (writing_mode) { - case WritingMode::kHorizontalTb: - return NGLineOrientation::kHorizontal; - case WritingMode::kVerticalRl: - case WritingMode::kVerticalLr: - case WritingMode::kSidewaysRl: - return NGLineOrientation::kClockWiseVertical; - case WritingMode::kSidewaysLr: - return NGLineOrientation::kCounterClockWiseVertical; - } - NOTREACHED(); - return NGLineOrientation::kHorizontal; -} - -} // namespace - -NGTextFragmentBuilder::NGTextFragmentBuilder(NGInlineNode node, - WritingMode writing_mode) - : NGBaseFragmentBuilder(writing_mode, TextDirection::kLtr), - inline_node_(node) {} - void NGTextFragmentBuilder::SetItem( NGPhysicalTextFragment::NGTextType text_type, const NGInlineItemsData& items_data, @@ -62,7 +39,7 @@ void NGTextFragmentBuilder::SetText( const String& text, scoped_refptr<const ComputedStyle> style, bool is_ellipsis_style, - scoped_refptr<const ShapeResult> shape_result) { + scoped_refptr<const ShapeResultView> shape_result) { DCHECK(layout_object); DCHECK(style); DCHECK(shape_result); @@ -70,8 +47,8 @@ void NGTextFragmentBuilder::SetText( text_type_ = NGPhysicalTextFragment::kGeneratedText; text_ = text; item_index_ = std::numeric_limits<unsigned>::max(); - start_offset_ = shape_result->StartIndexForResult(); - end_offset_ = shape_result->EndIndexForResult(); + start_offset_ = shape_result->StartIndex(); + end_offset_ = shape_result->EndIndex(); SetStyle(style, is_ellipsis_style ? NGStyleVariant::kEllipsis : NGStyleVariant::kStandard); size_ = {shape_result->SnappedWidth(), @@ -84,11 +61,7 @@ void NGTextFragmentBuilder::SetText( scoped_refptr<const NGPhysicalTextFragment> NGTextFragmentBuilder::ToTextFragment() { scoped_refptr<const NGPhysicalTextFragment> fragment = - base::AdoptRef(new NGPhysicalTextFragment( - layout_object_, Style(), style_variant_, text_type_, text_, - start_offset_, end_offset_, size_.ConvertToPhysical(GetWritingMode()), - ToLineOrientation(GetWritingMode()), end_effect_, - std::move(shape_result_))); + base::AdoptRef(new NGPhysicalTextFragment(this)); return fragment; } 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 2621674b787..bf000858f53 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 @@ -9,20 +9,22 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_end_effect.h" -#include "third_party/blink/renderer/core/layout/ng/ng_base_fragment_builder.h" +#include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h" #include "third_party/blink/renderer/platform/wtf/allocator.h" namespace blink { class LayoutObject; -class ShapeResult; +class ShapeResultView; struct NGInlineItemResult; -class CORE_EXPORT NGTextFragmentBuilder final : public NGBaseFragmentBuilder { +class CORE_EXPORT NGTextFragmentBuilder final : public NGFragmentBuilder { STACK_ALLOCATED(); public: - NGTextFragmentBuilder(NGInlineNode, WritingMode); + NGTextFragmentBuilder(NGInlineNode node, WritingMode writing_mode) + : NGFragmentBuilder(writing_mode, TextDirection::kLtr), + inline_node_(node) {} // NOTE: Takes ownership of the shape result within the item result. void SetItem(NGPhysicalTextFragment::NGTextType, @@ -35,7 +37,7 @@ class CORE_EXPORT NGTextFragmentBuilder final : public NGBaseFragmentBuilder { const String& text, scoped_refptr<const ComputedStyle>, bool is_ellipsis_style, - scoped_refptr<const ShapeResult>); + scoped_refptr<const ShapeResultView>); // Creates the fragment. Can only be called once. scoped_refptr<const NGPhysicalTextFragment> ToTextFragment(); @@ -46,14 +48,14 @@ class CORE_EXPORT NGTextFragmentBuilder final : public NGBaseFragmentBuilder { unsigned item_index_; unsigned start_offset_; unsigned end_offset_; - NGLogicalSize size_; - scoped_refptr<const ShapeResult> shape_result_; + scoped_refptr<const ShapeResultView> shape_result_; NGPhysicalTextFragment::NGTextType text_type_ = NGPhysicalTextFragment::kNormalText; NGTextEndEffect end_effect_ = NGTextEndEffect::kNone; - LayoutObject* layout_object_ = nullptr; + + friend class NGPhysicalTextFragment; }; } // namespace blink |