summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/layout/ng/inline
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-01-20 13:40:20 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-01-22 12:41:23 +0000
commit7961cea6d1041e3e454dae6a1da660b453efd238 (patch)
treec0eeb4a9ff9ba32986289c1653d9608e53ccb444 /chromium/third_party/blink/renderer/core/layout/ng/inline
parentb7034d0803538058e5c9d904ef03cf5eab34f6ef (diff)
downloadqtwebengine-chromium-7961cea6d1041e3e454dae6a1da660b453efd238.tar.gz
BASELINE: Update Chromium to 78.0.3904.130
Change-Id: If185e0c0061b3437531c97c9c8c78f239352a68b Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/layout/ng/inline')
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc270
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc7
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h1
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h1
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc155
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h231
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item_test.cc123
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc16
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h40
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc151
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h84
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc100
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h9
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc2
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h10
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc181
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h98
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc128
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h5
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc28
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc86
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h2
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc51
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc564
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h43
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc41
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc14
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h25
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc207
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h24
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc31
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc10
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h6
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc84
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h8
35 files changed, 2483 insertions, 353 deletions
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc
new file mode 100644
index 00000000000..da4b412bbff
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc
@@ -0,0 +1,270 @@
+// Copyright 2019 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/layout_ng_text.h"
+
+#include <sstream>
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/layout/layout_block_flow.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_node_data.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+
+namespace blink {
+
+class LayoutNGTextTest : public PageTestBase {
+ protected:
+ std::string GetItemsAsString(const LayoutText& layout_text) {
+ if (layout_text.NeedsCollectInlines())
+ return "LayoutText has NeedsCollectInlines";
+ if (!layout_text.HasValidInlineItems())
+ return "No valid inline items in LayoutText";
+ const LayoutBlockFlow& block_flow = *layout_text.ContainingNGBlockFlow();
+ if (block_flow.NeedsCollectInlines())
+ return "LayoutBlockFlow has NeedsCollectInlines";
+ const NGInlineNodeData& data = *block_flow.GetNGInlineNodeData();
+ std::ostringstream stream;
+ for (const NGInlineItem& item : data.items) {
+ if (item.Type() != NGInlineItem::kText)
+ continue;
+ if (item.GetLayoutObject() == layout_text)
+ stream << "*";
+ stream << "{'"
+ << data.text_content.Substring(item.StartOffset(), item.Length())
+ .Utf8()
+ << "'";
+ if (item.TextShapeResult()) {
+ stream << ", ShapeResult=" << item.TextShapeResult()->StartIndex()
+ << "+" << item.TextShapeResult()->NumCharacters();
+ }
+ stream << "}" << std::endl;
+ }
+ return stream.str();
+ }
+};
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetAppendCollapseWhiteSpace) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<p id=target>abc </p>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild());
+ text.appendData("XYZ");
+
+ EXPECT_EQ("*{'abc XYZ', ShapeResult=0+7}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetAppend) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<pre id=target><a>abc</a>XYZ<b>def</b></pre>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild()->nextSibling());
+ text.appendData("xyz");
+
+ EXPECT_EQ(
+ "{'abc', ShapeResult=0+3}\n"
+ "*{'XYZxyz', ShapeResult=3+6}\n"
+ "{'def', ShapeResult=9+3}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetDelete) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<pre id=target><a>abc</a>xXYZyz<b>def</b></pre>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild()->nextSibling());
+ text.deleteData(1, 3, ASSERT_NO_EXCEPTION);
+
+ EXPECT_EQ(
+ "{'abc', ShapeResult=0+3}\n"
+ "*{'xyz', ShapeResult=3+3}\n"
+ "{'def', ShapeResult=6+3}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteCollapseWhiteSpace) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<p id=target>ab XY cd</p>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild());
+ text.deleteData(4, 2, ASSERT_NO_EXCEPTION); // remove "XY"
+
+ EXPECT_EQ("*{'ab cd', ShapeResult=0+5}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteCollapseWhiteSpaceEnd) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<p id=target>a bc</p>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild());
+ text.deleteData(2, 2, ASSERT_NO_EXCEPTION); // remove "bc"
+
+ EXPECT_EQ("*{'a', ShapeResult=0+1}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteRTL) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<p id=target dir=rtl>0 234</p>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild());
+ text.deleteData(2, 2, ASSERT_NO_EXCEPTION); // remove "23"
+
+ EXPECT_EQ("*{'0 4', ShapeResult=0+3}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetInsert) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<pre id=target><a>abc</a>XYZ<b>def</b></pre>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild()->nextSibling());
+ text.insertData(1, "xyz", ASSERT_NO_EXCEPTION);
+
+ EXPECT_EQ(
+ "{'abc', ShapeResult=0+3}\n"
+ "*{'XxyzYZ', ShapeResult=3+6}\n"
+ "{'def', ShapeResult=9+3}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetInsertAfterSpace) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<p id=target>ab cd</p>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild());
+ text.insertData(3, " XYZ ", ASSERT_NO_EXCEPTION);
+
+ EXPECT_EQ("*{'ab XYZ cd', ShapeResult=0+9}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetInserBeforetSpace) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<p id=target>ab cd</p>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild());
+ text.insertData(2, " XYZ ", ASSERT_NO_EXCEPTION);
+
+ EXPECT_EQ("*{'ab XYZ cd', ShapeResult=0+9}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetNoRelocation) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<pre id=target><a>abc</a>XYZ<b>def</b></pre>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild()->nextSibling());
+ // Note: |CharacterData::setData()| is implementation of Node::setNodeValue()
+ // for |CharacterData|.
+ text.setData("xyz");
+
+ EXPECT_EQ("LayoutText has NeedsCollectInlines",
+ GetItemsAsString(*text.GetLayoutObject()))
+ << "There are no optimization for setData()";
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetPrepend) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<pre id=target><a>abc</a>XYZ<b>def</b></pre>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild()->nextSibling());
+ text.insertData(1, "xyz", ASSERT_NO_EXCEPTION);
+
+ EXPECT_EQ(
+ "{'abc', ShapeResult=0+3}\n"
+ "*{'XxyzYZ', ShapeResult=3+6}\n"
+ "{'def', ShapeResult=9+3}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetReplace) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<pre id=target><a>abc</a>XYZW<b>def</b></pre>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild()->nextSibling());
+ text.replaceData(1, 2, "yz", ASSERT_NO_EXCEPTION);
+
+ EXPECT_EQ(
+ "{'abc', ShapeResult=0+3}\n"
+ "*{'XyzW', ShapeResult=3+4}\n"
+ "{'def', ShapeResult=7+3}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetReplaceCollapseWhiteSpace) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<p id=target>ab XY cd</p>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild());
+ text.replaceData(4, 2, " ", ASSERT_NO_EXCEPTION); // replace "XY" to " "
+
+ EXPECT_EQ("*{'ab cd', ShapeResult=0+5}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetReplaceToExtend) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<pre id=target><a>abc</a>XYZW<b>def</b></pre>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild()->nextSibling());
+ text.replaceData(1, 2, "xyz", ASSERT_NO_EXCEPTION);
+
+ EXPECT_EQ(
+ "{'abc', ShapeResult=0+3}\n"
+ "*{'XxyzW', ShapeResult=3+5}\n"
+ "{'def', ShapeResult=8+3}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetReplaceToShrink) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<pre id=target><a>abc</a>XYZW<b>def</b></pre>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild()->nextSibling());
+ text.replaceData(1, 2, "y", ASSERT_NO_EXCEPTION);
+
+ EXPECT_EQ(
+ "{'abc', ShapeResult=0+3}\n"
+ "*{'XyW', ShapeResult=3+3}\n"
+ "{'def', ShapeResult=6+3}\n",
+ GetItemsAsString(*text.GetLayoutObject()));
+}
+
+TEST_F(LayoutNGTextTest, SetTextWithOffsetToEmpty) {
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return;
+
+ SetBodyInnerHTML(u"<pre id=target><a>abc</a>XYZ<b>def</b></pre>");
+ Text& text = To<Text>(*GetElementById("target")->firstChild()->nextSibling());
+ // Note: |CharacterData::setData()| is implementation of Node::setNodeValue()
+ // for |CharacterData|.
+ // Note: |setData()| detaches layout object from |Text| node since
+ // |Text::TextLayoutObjectIsNeeded()| returns false for empty text.
+ text.setData("");
+ UpdateAllLifecyclePhasesForTest();
+
+ EXPECT_EQ(nullptr, text.GetLayoutObject());
+}
+
+} // namespace blink
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 4c1c7ee28da..cf430a648e4 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
@@ -256,4 +256,11 @@ scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::PreviousOnLine()
return nullptr;
}
+bool NGAbstractInlineTextBox::IsLineBreak() const {
+ if (!fragment_)
+ return false;
+ DCHECK(!NeedsLayout());
+ return PhysicalTextFragment().IsLineBreak();
+}
+
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h
index fb2368ed230..551e5642114 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h
@@ -50,6 +50,7 @@ class CORE_EXPORT NGAbstractInlineTextBox final : public AbstractInlineTextBox {
bool IsLast() const final;
scoped_refptr<AbstractInlineTextBox> NextOnLine() const final;
scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const final;
+ bool IsLineBreak() const final;
const NGPaintFragment* fragment_;
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 e527615ce8b..4f4a1978af6 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,6 +5,7 @@
#ifndef NGBaseline_h
#define NGBaseline_h
+#include "base/optional.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/geometry/layout_unit.h"
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc
new file mode 100644
index 00000000000..35aa96a8c03
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc
@@ -0,0 +1,155 @@
+// Copyright 2019 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_fragment_item.h"
+
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
+
+namespace blink {
+
+NGFragmentItem::NGFragmentItem(const NGPhysicalTextFragment& text)
+ : layout_object_(text.GetLayoutObject()),
+ text_({text.TextShapeResult(), text.StartOffset(), text.EndOffset()}),
+ rect_({PhysicalOffset(), text.Size()}),
+ type_(kText),
+ style_variant_(static_cast<unsigned>(text.StyleVariant())),
+ is_hidden_for_paint_(false) {
+ DCHECK_LE(text_.start_offset, text_.end_offset);
+#if DCHECK_IS_ON()
+ if (text_.shape_result) {
+ DCHECK_EQ(text_.shape_result->StartIndex(), text_.start_offset);
+ DCHECK_EQ(text_.shape_result->EndIndex(), text_.end_offset);
+ }
+#endif
+}
+
+NGFragmentItem::NGFragmentItem(const NGPhysicalLineBoxFragment& line,
+ wtf_size_t item_count)
+ : layout_object_(nullptr),
+ line_({line.Metrics(), To<NGInlineBreakToken>(line.BreakToken()),
+ item_count}),
+ rect_({PhysicalOffset(), line.Size()}),
+ type_(kLine),
+ style_variant_(static_cast<unsigned>(line.StyleVariant())),
+ is_hidden_for_paint_(false) {}
+
+NGFragmentItem::NGFragmentItem(const NGPhysicalBoxFragment& box,
+ wtf_size_t item_count)
+ : layout_object_(box.GetLayoutObject()),
+ box_({&box, item_count}),
+ rect_({PhysicalOffset(), box.Size()}),
+ type_(kBox),
+ style_variant_(static_cast<unsigned>(box.StyleVariant())),
+ is_hidden_for_paint_(false) {}
+
+NGFragmentItem::~NGFragmentItem() {
+ switch (Type()) {
+ case kText:
+ text_.~TextItem();
+ break;
+ case kGeneratedText:
+ generated_text_.~GeneratedTextItem();
+ break;
+ case kLine:
+ line_.~LineItem();
+ break;
+ case kBox:
+ box_.~BoxItem();
+ break;
+ }
+}
+
+PhysicalRect NGFragmentItem::SelfInkOverflow() const {
+ // TODO(kojii): Implement.
+ return LocalRect();
+}
+
+StringView NGFragmentItem::Text(const NGFragmentItems& items) const {
+ if (Type() == kText) {
+ DCHECK_LE(text_.start_offset, text_.end_offset);
+ return StringView(items.Text(UsesFirstLineStyle()), text_.start_offset,
+ text_.end_offset - text_.start_offset);
+ }
+ NOTREACHED();
+ return StringView();
+}
+
+NGTextFragmentPaintInfo NGFragmentItem::TextPaintInfo(
+ const NGFragmentItems& items) const {
+ if (Type() == kText) {
+ return {items.Text(UsesFirstLineStyle()), text_.start_offset,
+ text_.end_offset, text_.shape_result.get()};
+ }
+ NOTREACHED();
+ return {};
+}
+
+String NGFragmentItem::DebugName() const {
+ return "NGFragmentItem";
+}
+
+IntRect NGFragmentItem::VisualRect() const {
+ // TODO(kojii): Need to reconsider the storage of |VisualRect|, to integrate
+ // better with |FragmentData| and to avoid dependency to |LayoutObject|.
+ return GetLayoutObject()->VisualRectForInlineBox();
+}
+
+PhysicalRect NGFragmentItem::LocalVisualRectFor(
+ const LayoutObject& layout_object) {
+ DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled());
+ DCHECK(layout_object.IsInLayoutNGInlineFormattingContext());
+
+ PhysicalRect visual_rect;
+ for (const NGFragmentItem& item : ItemsFor(layout_object)) {
+ if (UNLIKELY(item.IsHiddenForPaint()))
+ continue;
+ PhysicalRect child_visual_rect = item.SelfInkOverflow();
+ child_visual_rect.offset += item.Offset();
+ visual_rect.Unite(child_visual_rect);
+ }
+ return visual_rect;
+}
+
+NGFragmentItem::ItemsForLayoutObject NGFragmentItem::ItemsFor(
+ const LayoutObject& layout_object) {
+ DCHECK(layout_object.IsInLayoutNGInlineFormattingContext());
+ DCHECK(layout_object.IsText() || layout_object.IsLayoutInline() ||
+ (layout_object.IsBox() && layout_object.IsInline()));
+
+ // TODO(kojii): This is a hot function needed by paint and several other
+ // operations. Make this fast, by not iterating.
+ if (const LayoutBlockFlow* block_flow =
+ layout_object.RootInlineFormattingContext()) {
+ if (const NGPhysicalBoxFragment* fragment = block_flow->CurrentFragment()) {
+ if (const NGFragmentItems* items = fragment->Items()) {
+ for (unsigned i = 0; i < items->Items().size(); ++i) {
+ const NGFragmentItem* item = items->Items()[i].get();
+ if (item->GetLayoutObject() == &layout_object)
+ return ItemsForLayoutObject(items->Items(), i, item);
+ }
+ }
+ }
+ }
+
+ return ItemsForLayoutObject();
+}
+
+NGFragmentItem::ItemsForLayoutObject::Iterator&
+NGFragmentItem::ItemsForLayoutObject::Iterator::operator++() {
+ // TODO(kojii): This is a hot function needed by paint and several other
+ // operations. Make this fast, by not iterating.
+ if (!current_)
+ return *this;
+ const LayoutObject* current_layout_object = current_->GetLayoutObject();
+ while (++index_ < items_->size()) {
+ current_ = (*items_)[index_].get();
+ if (current_->GetLayoutObject() == current_layout_object)
+ return *this;
+ }
+ current_ = nullptr;
+ return *this;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h
new file mode 100644
index 00000000000..cf7cfc2803a
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h
@@ -0,0 +1,231 @@
+// Copyright 2019 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_FRAGMENT_ITEM_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_FRAGMENT_ITEM_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h"
+#include "third_party/blink/renderer/core/layout/layout_object.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_line_height_metrics.h"
+#include "third_party/blink/renderer/platform/graphics/paint/display_item_client.h"
+
+namespace blink {
+
+class NGFragmentItems;
+class NGInlineBreakToken;
+struct NGTextFragmentPaintInfo;
+
+// This class represents a text run or a box in an inline formatting context.
+//
+// This class consumes less memory than a full fragment, and can be stored in a
+// flat list (NGFragmentItems) for easier and faster traversal.
+class CORE_EXPORT NGFragmentItem : public DisplayItemClient {
+ public:
+ // Represents regular text that exists in the DOM.
+ struct TextItem {
+ scoped_refptr<const ShapeResultView> shape_result;
+ // TODO(kojii): |start_offset| and |end_offset| should match to the offset
+ // in |shape_result|. Consider if we should remove them, or if keeping them
+ // is easier.
+ const unsigned start_offset;
+ const unsigned end_offset;
+ };
+ // Represents text generated by the layout engine, e.g., hyphen or ellipsis.
+ struct GeneratedTextItem {
+ scoped_refptr<const ShapeResultView> shape_result;
+ String text;
+ };
+ // A start marker of a line box.
+ struct LineItem {
+ NGLineHeightMetrics metrics;
+ scoped_refptr<const NGInlineBreakToken> inline_break_token;
+ wtf_size_t children_count;
+ };
+ // Represents a box fragment appeared in a line. This includes inline boxes
+ // (e.g., <span>text</span>) and atomic inlines.
+ struct BoxItem {
+ // If this item is an inline box, its children are stored as following
+ // items. |children_count_| has the number of such items.
+ //
+ // If this item is a root of another IFC/BFC, children are stored normally,
+ // as children of |box_fragment|.
+ scoped_refptr<const NGPhysicalBoxFragment> box_fragment;
+ wtf_size_t children_count;
+ };
+
+ enum ItemType { kText, kGeneratedText, kLine, kBox };
+
+ // TODO(kojii): Should be able to create without once creating fragments.
+ NGFragmentItem(const NGPhysicalTextFragment& text);
+ NGFragmentItem(const NGPhysicalBoxFragment& box, wtf_size_t item_count);
+ NGFragmentItem(const NGPhysicalLineBoxFragment& line, wtf_size_t item_count);
+
+ ~NGFragmentItem() final;
+
+ ItemType Type() const { return static_cast<ItemType>(type_); }
+
+ bool IsHiddenForPaint() const { return is_hidden_for_paint_; }
+
+ NGStyleVariant StyleVariant() const {
+ return static_cast<NGStyleVariant>(style_variant_);
+ }
+ bool UsesFirstLineStyle() const {
+ return StyleVariant() == NGStyleVariant::kFirstLine;
+ }
+ // Returns the style for this fragment.
+ //
+ // For a line box, this returns the style of the containing block. This mostly
+ // represents the style for the line box, except 1) |style.Direction()| maybe
+ // incorrect, use |BaseDirection()| instead, and 2) margin/border/padding,
+ // background etc. do not apply to the line box.
+ const ComputedStyle& Style() const {
+ return layout_object_->EffectiveStyle(StyleVariant());
+ }
+ const LayoutObject* GetLayoutObject() const { return layout_object_; }
+
+ const PhysicalRect& Rect() const { return rect_; }
+ const PhysicalOffset& Offset() const { return rect_.offset; }
+ const PhysicalSize& Size() const { return rect_.size; }
+ void SetOffset(const PhysicalOffset& offset) { rect_.offset = offset; }
+
+ PhysicalRect LocalRect() const { return {PhysicalOffset(), Size()}; }
+ PhysicalRect SelfInkOverflow() const;
+
+ // Count of following items that are descendants of this item in the box tree,
+ // including this item. 1 means this is a box (box or line box) without
+ // children. 0 if this item type cannot have children.
+ wtf_size_t ChildrenCount() const {
+ if (Type() == kBox)
+ return box_.children_count;
+ if (Type() == kLine)
+ return line_.children_count;
+ return 0;
+ }
+
+ // Returns |NGPhysicalBoxFragment| if one is associated with this item.
+ const NGPhysicalBoxFragment* BoxFragment() const {
+ if (Type() == kBox)
+ return box_.box_fragment.get();
+ return nullptr;
+ }
+
+ unsigned TextLength() const {
+ DCHECK_EQ(Type(), kText);
+ return text_.end_offset - text_.start_offset;
+ }
+ StringView Text(const NGFragmentItems& items) const;
+ NGTextFragmentPaintInfo TextPaintInfo(const NGFragmentItems& items) const;
+
+ // DisplayItemClient overrides
+ String DebugName() const override;
+ IntRect VisualRect() const override;
+
+ // Find |NGFragmentItem|s that are associated with a |LayoutObject|.
+ class CORE_EXPORT ItemsForLayoutObject {
+ STACK_ALLOCATED();
+
+ public:
+ ItemsForLayoutObject() = default;
+ ItemsForLayoutObject(const Vector<std::unique_ptr<NGFragmentItem>>& items,
+ unsigned first_index,
+ const NGFragmentItem* first_item)
+ : items_(&items), first_item_(first_item), first_index_(first_index) {}
+
+ bool IsEmpty() const { return !items_; }
+
+ class CORE_EXPORT Iterator {
+ public:
+ Iterator(const Vector<std::unique_ptr<NGFragmentItem>>* items,
+ unsigned index,
+ const NGFragmentItem* item)
+ : current_(item), items_(items), index_(index) {}
+ const NGFragmentItem& operator*() const { return *current_; }
+ const NGFragmentItem& operator->() const { return *current_; }
+ Iterator& operator++();
+ bool operator==(const Iterator& other) const {
+ return current_ == other.current_;
+ }
+ bool operator!=(const Iterator& other) const {
+ return current_ != other.current_;
+ }
+
+ private:
+ const NGFragmentItem* current_;
+ const Vector<std::unique_ptr<NGFragmentItem>>* items_;
+ unsigned index_;
+ };
+ using iterator = Iterator;
+ iterator begin() const {
+ return Iterator(items_, first_index_, first_item_);
+ }
+ iterator end() const { return Iterator(nullptr, 0, nullptr); }
+
+ private:
+ const Vector<std::unique_ptr<NGFragmentItem>>* items_;
+ const NGFragmentItem* first_item_;
+ unsigned first_index_;
+ };
+ static ItemsForLayoutObject ItemsFor(const LayoutObject& layout_object);
+ static PhysicalRect LocalVisualRectFor(const LayoutObject& layout_object);
+
+ // Painters can use const methods only, except for these explicitly declared
+ // methods.
+ class MutableForPainting {
+ STACK_ALLOCATED();
+
+ public:
+ // TODO(kojii): Add painter functions.
+
+ private:
+ friend class NGFragmentItem;
+ MutableForPainting(const NGFragmentItem& item) {}
+ };
+ MutableForPainting GetMutableForPainting() const {
+ return MutableForPainting(*this);
+ }
+
+ private:
+ const LayoutObject* layout_object_;
+
+ // TODO(kojii): We can make them sub-classes if we need to make the vector of
+ // pointers. Sub-classing from DisplayItemClient prohibits copying and that we
+ // cannot create a vector of this class.
+ union {
+ TextItem text_;
+ GeneratedTextItem generated_text_;
+ LineItem line_;
+ BoxItem box_;
+ };
+
+ PhysicalRect rect_;
+
+ struct NGInkOverflowModel {
+ USING_FAST_MALLOC(NGInkOverflowModel);
+
+ public:
+ NGInkOverflowModel(const PhysicalRect& self_ink_overflow,
+ const PhysicalRect& contents_ink_overflow);
+
+ PhysicalRect self_ink_overflow;
+ // TODO(kojii): Some types (e.g., kText) never have |contents_ink_overflow|.
+ // Can/should we optimize the memory usage for those cases?
+ PhysicalRect contents_ink_overflow;
+ };
+ mutable std::unique_ptr<NGInkOverflowModel> ink_overflow_;
+ // TOOD(kojii): mutable because this is lazily computed, but it may not be
+ // needed if we use |MutableForPainting|. TBD.
+
+ // Item index delta to the next item for the same |LayoutObject|.
+ // wtf_size_t delta_to_next_for_same_layout_object_ = 0;
+
+ unsigned type_ : 2; // ItemType
+ unsigned style_variant_ : 2; // NGStyleVariant
+ unsigned is_hidden_for_paint_ : 1;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_FRAGMENT_ITEM_H_
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item_test.cc
new file mode 100644
index 00000000000..bc573b0d0d1
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item_test.cc
@@ -0,0 +1,123 @@
+// Copyright 2019 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_fragment_item.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.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"
+
+using testing::ElementsAre;
+
+namespace blink {
+
+class NGFragmentItemTest : public NGLayoutTest,
+ ScopedLayoutNGFragmentItemForTest {
+ public:
+ NGFragmentItemTest() : ScopedLayoutNGFragmentItemForTest(true) {}
+
+ Vector<const NGFragmentItem*> ItemsForAsVector(
+ const LayoutObject& layout_object) {
+ const auto items = NGFragmentItem::ItemsFor(layout_object);
+ Vector<const NGFragmentItem*> list;
+ for (const NGFragmentItem& item : items) {
+ EXPECT_EQ(item.GetLayoutObject(), &layout_object);
+ list.push_back(&item);
+ }
+ return list;
+ }
+};
+
+TEST_F(NGFragmentItemTest, BasicText) {
+ LoadAhem();
+ SetBodyInnerHTML(R"HTML(
+ <style>
+ html, body {
+ margin: 0;
+ font-family: Ahem;
+ font-size: 10px;
+ line-height: 1;
+ }
+ div {
+ width: 10ch;
+ }
+ </style>
+ <div id="container">
+ 1234567 98765
+ </div>
+ )HTML");
+
+ LayoutBlockFlow* container =
+ To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"));
+ LayoutText* layout_text = ToLayoutText(container->FirstChild());
+ const NGPhysicalBoxFragment* box = container->CurrentFragment();
+ EXPECT_NE(box, nullptr);
+ const NGFragmentItems* items = box->Items();
+ EXPECT_NE(items, nullptr);
+ EXPECT_EQ(items->Items().size(), 4u);
+
+ // The text node wraps, produces two fragments.
+ Vector<const NGFragmentItem*> items_for_text = ItemsForAsVector(*layout_text);
+ EXPECT_EQ(items_for_text.size(), 2u);
+
+ const NGFragmentItem& text1 = *items_for_text[0];
+ EXPECT_EQ(text1.Type(), NGFragmentItem::kText);
+ EXPECT_EQ(text1.GetLayoutObject(), layout_text);
+ EXPECT_EQ(text1.Offset(), PhysicalOffset());
+
+ const NGFragmentItem& text2 = *items_for_text[1];
+ EXPECT_EQ(text2.Type(), NGFragmentItem::kText);
+ EXPECT_EQ(text2.GetLayoutObject(), layout_text);
+ EXPECT_EQ(text2.Offset(), PhysicalOffset(0, 10));
+
+ EXPECT_EQ(IntRect(0, 0, 70, 20),
+ layout_text->FragmentsVisualRectBoundingBox());
+}
+
+TEST_F(NGFragmentItemTest, BasicInlineBox) {
+ LoadAhem();
+ SetBodyInnerHTML(R"HTML(
+ <style>
+ html, body {
+ margin: 0;
+ font-family: Ahem;
+ font-size: 10px;
+ line-height: 1;
+ }
+ #container {
+ width: 10ch;
+ }
+ #span1, #span2 {
+ background: gray;
+ }
+ </style>
+ <div id="container">
+ 000
+ <span id="span1">1234 5678</span>
+ 999
+ <span id="span2">12345678</span>
+ </div>
+ )HTML");
+
+ // "span1" wraps, produces two fragments.
+ const LayoutObject* span1 = GetLayoutObjectByElementId("span1");
+ ASSERT_NE(span1, nullptr);
+ Vector<const NGFragmentItem*> items_for_span1 = ItemsForAsVector(*span1);
+ EXPECT_EQ(items_for_span1.size(), 2u);
+
+ EXPECT_EQ(IntRect(0, 0, 80, 20), span1->FragmentsVisualRectBoundingBox());
+
+ // "span2" doesn't wrap, produces only one fragment.
+ const LayoutObject* span2 = GetLayoutObjectByElementId("span2");
+ ASSERT_NE(span2, nullptr);
+ Vector<const NGFragmentItem*> items_for_span2 = ItemsForAsVector(*span2);
+ EXPECT_EQ(items_for_span2.size(), 1u);
+
+ EXPECT_EQ(IntRect(0, 20, 80, 10), span2->FragmentsVisualRectBoundingBox());
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc
new file mode 100644
index 00000000000..87aba81920f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc
@@ -0,0 +1,16 @@
+// Copyright 2019 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_fragment_items.h"
+
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h"
+
+namespace blink {
+
+NGFragmentItems::NGFragmentItems(NGFragmentItemsBuilder* builder)
+ : items_(std::move(builder->items_)),
+ text_content_(std::move(builder->text_content_)),
+ first_line_text_content_(std::move(builder->first_line_text_content_)) {}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h
new file mode 100644
index 00000000000..fe62f758e81
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h
@@ -0,0 +1,40 @@
+// Copyright 2019 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_FRAGMENT_ITEMS_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_FRAGMENT_ITEMS_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h"
+
+namespace blink {
+
+class NGFragmentItemsBuilder;
+
+// Represents the inside of an inline formatting context.
+//
+// During the layout phase, descendants of the inline formatting context is
+// transformed to a flat list of |NGFragmentItem| and stored in this class.
+class CORE_EXPORT NGFragmentItems {
+ public:
+ NGFragmentItems(NGFragmentItemsBuilder* builder);
+
+ const Vector<std::unique_ptr<NGFragmentItem>>& Items() const {
+ return items_;
+ }
+
+ const String& Text(bool first_line) const {
+ return UNLIKELY(first_line) ? first_line_text_content_ : text_content_;
+ }
+
+ private:
+ // TODO(kojii): inline capacity TBD.
+ Vector<std::unique_ptr<NGFragmentItem>> items_;
+ String text_content_;
+ String first_line_text_content_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_FRAGMENT_ITEMS_H_
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc
new file mode 100644
index 00000000000..31a90c76b39
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc
@@ -0,0 +1,151 @@
+// Copyright 2019 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_fragment_items_builder.h"
+
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
+
+namespace blink {
+
+void NGFragmentItemsBuilder::SetTextContent(const NGInlineNode& node) {
+ const NGInlineItemsData& items_data = node.ItemsData(false);
+ text_content_ = items_data.text_content;
+ const NGInlineItemsData& first_line = node.ItemsData(true);
+ if (&items_data != &first_line)
+ first_line_text_content_ = first_line.text_content;
+}
+
+void NGFragmentItemsBuilder::SetCurrentLine(
+ const NGPhysicalLineBoxFragment& line,
+ ChildList&& children) {
+#if DCHECK_IS_ON()
+ current_line_fragment_ = &line;
+#endif
+ current_line_ = std::move(children);
+}
+
+void NGFragmentItemsBuilder::AddLine(const NGPhysicalLineBoxFragment& line,
+ const LogicalOffset& offset) {
+ DCHECK_EQ(items_.size(), offsets_.size());
+#if DCHECK_IS_ON()
+ DCHECK(!is_converted_to_physical_);
+ DCHECK_EQ(current_line_fragment_, &line);
+#endif
+
+ // Reserve the capacity for (children + line box item).
+ wtf_size_t size_before = items_.size();
+ wtf_size_t capacity = size_before + current_line_.size() + 1;
+ items_.ReserveCapacity(capacity);
+ offsets_.ReserveCapacity(capacity);
+
+ // Add an empty item so that the start of the line can be set later.
+ wtf_size_t line_start_index = items_.size();
+ items_.Grow(line_start_index + 1);
+ offsets_.Grow(line_start_index + 1);
+
+ AddItems(current_line_.begin(), current_line_.end());
+
+ // All children are added. Create an item for the start of the line.
+ wtf_size_t item_count = items_.size() - line_start_index;
+ items_[line_start_index] = std::make_unique<NGFragmentItem>(line, item_count);
+ // TODO(kojii): We probably need an end marker too for the reverse-order
+ // traversals.
+
+ for (unsigned i = size_before; i < offsets_.size(); ++i)
+ offsets_[i] += offset;
+
+ current_line_.clear();
+#if DCHECK_IS_ON()
+ current_line_fragment_ = nullptr;
+#endif
+}
+
+void NGFragmentItemsBuilder::AddItems(Child* child_begin, Child* child_end) {
+ DCHECK_EQ(items_.size(), offsets_.size());
+
+ for (Child* child_iter = child_begin; child_iter != child_end;) {
+ Child& child = *child_iter;
+ if (const NGPhysicalTextFragment* text = child.fragment.get()) {
+ items_.push_back(std::make_unique<NGFragmentItem>(*text));
+ offsets_.push_back(child.offset);
+ ++child_iter;
+ continue;
+ }
+
+ if (child.layout_result) {
+ // Create an item if this box has no inline children.
+ const NGPhysicalBoxFragment& box =
+ To<NGPhysicalBoxFragment>(child.layout_result->PhysicalFragment());
+ if (child.children_count <= 1) {
+ items_.push_back(std::make_unique<NGFragmentItem>(box, 1));
+ offsets_.push_back(child.offset);
+ ++child_iter;
+ continue;
+ }
+
+ // Children of inline boxes are flattened and added to |items_|, with the
+ // count of descendant items to preserve the tree structure.
+ //
+ // Add an empty item so that the start of the box can be set later.
+ wtf_size_t box_start_index = items_.size();
+ items_.Grow(box_start_index + 1);
+ offsets_.push_back(child.offset);
+
+ // Add all children, including their desendants, skipping this item.
+ CHECK_GE(child.children_count, 1u); // 0 will loop infinitely.
+ Child* end_child_iter = child_iter + child.children_count;
+ CHECK_LE(end_child_iter - child_begin, child_end - child_begin);
+ AddItems(child_iter + 1, end_child_iter);
+ child_iter = end_child_iter;
+
+ // All children are added. Compute how many items are actually added. The
+ // number of items added maybe different from |child.children_count|.
+ wtf_size_t item_count = items_.size() - box_start_index;
+
+ // Create an item for the start of the box.
+ items_[box_start_index] =
+ std::make_unique<NGFragmentItem>(box, item_count);
+ continue;
+ }
+
+ // OOF children should have been added to their parent box fragments.
+ // TODO(kojii): Consider handling them in NGFragmentItem too.
+ DCHECK(!child.out_of_flow_positioned_box);
+ ++child_iter;
+ }
+}
+
+// Convert internal logical offsets to physical. Items are kept with logical
+// offset until outer box size is determined.
+void NGFragmentItemsBuilder::ConvertToPhysical(WritingMode writing_mode,
+ TextDirection direction,
+ const PhysicalSize& outer_size) {
+ CHECK_EQ(items_.size(), offsets_.size());
+#if DCHECK_IS_ON()
+ DCHECK(!is_converted_to_physical_);
+#endif
+
+ const LogicalOffset* offset_iter = offsets_.begin();
+ for (auto& item : items_) {
+ item->SetOffset(offset_iter->ConvertToPhysical(writing_mode, direction,
+ outer_size, item->Size()));
+ ++offset_iter;
+ }
+
+#if DCHECK_IS_ON()
+ is_converted_to_physical_ = true;
+#endif
+}
+
+void NGFragmentItemsBuilder::ToFragmentItems(WritingMode writing_mode,
+ TextDirection direction,
+ const PhysicalSize& outer_size,
+ void* data) {
+ ConvertToPhysical(writing_mode, direction, outer_size);
+ new (data) NGFragmentItems(this);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h
new file mode 100644
index 00000000000..fcd9a1d5dce
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h
@@ -0,0 +1,84 @@
+// Copyright 2019 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_FRAGMENT_ITEMS_BUILDER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_FRAGMENT_ITEMS_BUILDER_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h"
+
+namespace blink {
+
+class NGBoxFragmentBuilder;
+class NGFragmentItem;
+class NGFragmentItems;
+class NGInlineNode;
+
+// This class builds |NGFragmentItems|.
+//
+// Once |NGFragmentItems| is built, it is immutable.
+class CORE_EXPORT NGFragmentItemsBuilder {
+ STACK_ALLOCATED();
+
+ public:
+ NGFragmentItemsBuilder(NGBoxFragmentBuilder* box_builder) {}
+
+ const String& TextContent(bool first_line) const {
+ return UNLIKELY(first_line && first_line_text_content_)
+ ? first_line_text_content_
+ : text_content_;
+ }
+ void SetTextContent(const NGInlineNode& node);
+
+ // The caller should create a |ChildList| for a complete line and add to this
+ // builder.
+ //
+ // Adding a line is a two-pass operation, because |NGInlineLayoutAlgorithm|
+ // creates and positions children within a line box, but its parent algorithm
+ // positions the line box. |SetCurrentLine| sets the children, and the next
+ // |AddLine| adds them.
+ //
+ // TODO(kojii): Moving |ChildList| is not cheap because it has inline
+ // capacity. Reconsider the ownership.
+ using Child = NGLineBoxFragmentBuilder::Child;
+ using ChildList = NGLineBoxFragmentBuilder::ChildList;
+ void SetCurrentLine(const NGPhysicalLineBoxFragment& line,
+ ChildList&& children);
+ void AddLine(const NGPhysicalLineBoxFragment& line,
+ const LogicalOffset& offset);
+
+ // Build a |NGFragmentItems|. The builder cannot build twice because data set
+ // to this builder may be cleared.
+ void ToFragmentItems(WritingMode writing_mode,
+ TextDirection direction,
+ const PhysicalSize& outer_size,
+ void* data);
+
+ private:
+ void AddItems(Child* child_begin, Child* child_end);
+
+ void ConvertToPhysical(WritingMode writing_mode,
+ TextDirection direction,
+ const PhysicalSize& outer_size);
+
+ Vector<std::unique_ptr<NGFragmentItem>> items_;
+ Vector<LogicalOffset> offsets_;
+ String text_content_;
+ String first_line_text_content_;
+
+ // Keeps children of a line until the offset is determined. See |AddLine|.
+ ChildList current_line_;
+
+#if DCHECK_IS_ON()
+ const NGPhysicalLineBoxFragment* current_line_fragment_ = nullptr;
+ bool is_converted_to_physical_ = false;
+#endif
+
+ friend class NGFragmentItems;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_FRAGMENT_ITEMS_BUILDER_H_
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 0bdaf394987..52a55b926ac 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
@@ -404,8 +404,6 @@ unsigned NGInlineLayoutStateStack::UpdateBoxDataFragmentRange(
// 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;
@@ -423,8 +421,6 @@ unsigned NGInlineLayoutStateStack::UpdateBoxDataFragmentRange(
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
@@ -572,18 +568,24 @@ void NGInlineLayoutStateStack::CreateBoxFragments(
unsigned start = box_data.fragment_start;
unsigned end = box_data.fragment_end;
DCHECK_GT(end, start);
- NGLineBoxFragmentBuilder::Child& start_child = (*line_box)[start];
+ NGLineBoxFragmentBuilder::Child* child = &(*line_box)[start];
scoped_refptr<const NGLayoutResult> box_fragment =
box_data.CreateBoxFragment(line_box);
- if (!start_child.HasFragment()) {
- start_child.layout_result = std::move(box_fragment);
- start_child.offset = box_data.offset;
+ if (!child->HasFragment()) {
+ child->layout_result = std::move(box_fragment);
+ child->offset = box_data.offset;
+ child->children_count = end - start;
} else {
// In most cases, |start_child| is moved to the children of the box, and
// is empty. It's not empty when it's out-of-flow. Insert in such case.
+ // TODO(kojii): With |NGFragmentItem|, all cases hit this code. Consider
+ // creating an empty item beforehand to avoid inserting.
line_box->InsertChild(start, std::move(box_fragment), box_data.offset,
LayoutUnit(), 0);
+ ChildInserted(start + 1);
+ child = &(*line_box)[start];
+ child->children_count = end - start + 1;
}
}
@@ -618,13 +620,7 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment(
for (unsigned i = fragment_start; i < fragment_end; i++) {
NGLineBoxFragmentBuilder::Child& child = (*line_box)[i];
- if (child.layout_result) {
- box.AddChild(child.layout_result->PhysicalFragment(),
- child.offset - offset);
- child.layout_result.reset();
- } else if (child.fragment) {
- box.AddChild(std::move(child.fragment), child.offset - offset);
- } else if (child.out_of_flow_positioned_box) {
+ if (child.out_of_flow_positioned_box) {
DCHECK(item->GetLayoutObject()->IsLayoutInline());
NGBlockNode oof_box(ToLayoutBox(child.out_of_flow_positioned_box));
@@ -636,6 +632,22 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment(
box.AddOutOfFlowChildCandidate(oof_box, static_offset,
child.container_direction);
child.out_of_flow_positioned_box = nullptr;
+ continue;
+ }
+
+ if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) {
+ // |NGFragmentItems| has a flat list of all descendants, except OOF
+ // objects. Still creates |NGPhysicalBoxFragment|, but don't add children
+ // to it and keep them in the flat list.
+ continue;
+ }
+
+ if (child.layout_result) {
+ box.AddChild(child.layout_result->PhysicalFragment(),
+ child.offset - offset);
+ child.layout_result.reset();
+ } else if (child.fragment) {
+ box.AddChild(std::move(child.fragment), child.offset - offset);
}
}
@@ -657,7 +669,7 @@ NGInlineLayoutStateStack::ApplyBaselineShift(
// |pending_descendants|.
LayoutUnit baseline_shift;
if (!box->pending_descendants.IsEmpty()) {
- NGLineHeightMetrics max = MetricsForTopAndBottomAlign(*box, *line_box);
+ bool has_top_or_bottom = false;
for (NGPendingPositions& child : box->pending_descendants) {
// In quirks mode, metrics is empty if no content.
if (child.metrics.IsEmpty())
@@ -666,9 +678,6 @@ NGInlineLayoutStateStack::ApplyBaselineShift(
case EVerticalAlign::kTextTop:
baseline_shift = child.metrics.ascent + box->TextTop(baseline_type);
break;
- case EVerticalAlign::kTop:
- baseline_shift = child.metrics.ascent - max.ascent;
- break;
case EVerticalAlign::kTextBottom:
if (const SimpleFontData* font_data =
box->style->GetFont().PrimaryFont()) {
@@ -678,10 +687,11 @@ NGInlineLayoutStateStack::ApplyBaselineShift(
break;
}
NOTREACHED();
- FALLTHROUGH;
- case EVerticalAlign::kBottom:
- baseline_shift = max.descent - child.metrics.descent;
break;
+ case EVerticalAlign::kTop:
+ case EVerticalAlign::kBottom:
+ has_top_or_bottom = true;
+ continue;
default:
NOTREACHED();
continue;
@@ -691,6 +701,32 @@ NGInlineLayoutStateStack::ApplyBaselineShift(
line_box->MoveInBlockDirection(baseline_shift, child.fragment_start,
child.fragment_end);
}
+ // `top` and `bottom` need to be applied after all other values are applied,
+ // because they align to the maximum metrics, but the maximum metrics may
+ // depend on other pending descendants for this box.
+ if (has_top_or_bottom) {
+ NGLineHeightMetrics max = MetricsForTopAndBottomAlign(*box, *line_box);
+ for (NGPendingPositions& child : box->pending_descendants) {
+ switch (child.vertical_align) {
+ case EVerticalAlign::kTop:
+ baseline_shift = child.metrics.ascent - max.ascent;
+ break;
+ case EVerticalAlign::kBottom:
+ baseline_shift = max.descent - child.metrics.descent;
+ break;
+ case EVerticalAlign::kTextTop:
+ case EVerticalAlign::kTextBottom:
+ continue;
+ default:
+ NOTREACHED();
+ continue;
+ }
+ child.metrics.Move(baseline_shift);
+ box->metrics.Unite(child.metrics);
+ line_box->MoveInBlockDirection(baseline_shift, child.fragment_start,
+ child.fragment_end);
+ }
+ }
box->pending_descendants.clear();
}
@@ -781,14 +817,26 @@ NGLineHeightMetrics NGInlineLayoutStateStack::MetricsForTopAndBottomAlign(
// BoxData contains inline boxes to be created later. Take them into account.
for (const BoxData& box_data : box_data_list_) {
+ // Except when the box has `vertical-align: top` or `bottom`.
+ DCHECK(box_data.item->Style());
+ const ComputedStyle& style = *box_data.item->Style();
+ EVerticalAlign vertical_align = style.VerticalAlign();
+ if (vertical_align == EVerticalAlign::kTop ||
+ vertical_align == EVerticalAlign::kBottom)
+ continue;
+
// |block_offset| is the top position when the baseline is at 0.
LayoutUnit box_ascent =
-line_box[box_data.fragment_end].offset.block_offset;
- LayoutUnit box_descent = box_data.size.block_size - box_ascent;
+ NGLineHeightMetrics box_metrics(box_ascent,
+ box_data.size.block_size - box_ascent);
// The top/bottom of inline boxes should not include their paddings.
- box_ascent -= box_data.padding.line_over;
- box_descent -= box_data.padding.line_under;
- metrics.Unite(NGLineHeightMetrics(box_ascent, box_descent));
+ box_metrics.ascent -= box_data.padding.line_over;
+ box_metrics.descent -= box_data.padding.line_under;
+ // Include the line-height property. The inline box has the height of the
+ // font metrics without the line-height included.
+ box_metrics.AddLeading(style.ComputedLineHeightAsFixed());
+ metrics.Unite(box_metrics);
}
// In quirks mode, metrics is empty if no content.
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 ba6599469b6..f5c93cd9d2b 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
@@ -69,15 +69,6 @@ class CORE_EXPORT NGInlineBreakToken final : public NGBreakToken {
return flags_ & kIsForcedBreak;
}
- // When a previously laid out line box didn't fit in the current
- // fragmentainer, and we have to lay it out again in the next fragmentainer,
- // we need to skip floats associated with that line. The parent block layout
- // algorithm will take care of any floats that broke and need to be resumed in
- // the next fragmentainer. Dealing with them as part of line layout as well
- // would result in duplicate fragments for the floats.
- void SetIgnoreFloats() { ignore_floats_ = true; }
- bool IgnoreFloats() const { return ignore_floats_; }
-
#if DCHECK_IS_ON()
String ToString() const override;
#endif
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc
index b1da84d71d0..888119d970d 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc
@@ -10,7 +10,7 @@ namespace {
struct SameSizeAsNGInlineChildLayoutContext {
base::Optional<NGInlineLayoutStateStack> box_states_;
- void* pointer;
+ void* pointers[2];
unsigned number;
};
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h
index 059776134d3..360714b2902 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h
@@ -5,6 +5,7 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_CHILD_LAYOUT_CONTEXT_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_CHILD_LAYOUT_CONTEXT_H_
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h"
namespace blink {
@@ -21,6 +22,11 @@ class NGInlineChildLayoutContext {
STACK_ALLOCATED();
public:
+ NGFragmentItemsBuilder* ItemsBuilder() { return items_builder_; }
+ void SetItemsBuilder(NGFragmentItemsBuilder* builder) {
+ items_builder_ = builder;
+ }
+
// Returns the NGInlineLayoutStateStack in this context.
bool HasBoxStates() const { return box_states_.has_value(); }
NGInlineLayoutStateStack* BoxStates() { return &*box_states_; }
@@ -40,6 +46,10 @@ class NGInlineChildLayoutContext {
}
private:
+ // TODO(kojii): Probably better to own |NGInlineChildLayoutContext|. While we
+ // transit, allocating separately is easier.
+ NGFragmentItemsBuilder* items_builder_ = nullptr;
+
base::Optional<NGInlineLayoutStateStack> box_states_;
// The items and its index this context is set up for.
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc
new file mode 100644
index 00000000000..74053c0f0ab
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc
@@ -0,0 +1,181 @@
+// Copyright 2019 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_inline_cursor.h"
+
+#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.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"
+
+namespace blink {
+
+void NGInlineCursor::MoveToItem(const ItemsSpan::iterator& iter) {
+ DCHECK((items_.data() || !items_.size()) && !root_paint_fragment_);
+ DCHECK(iter == items_.end() ||
+ (&*iter >= items_.data() && iter < items_.end()));
+ item_iter_ = iter;
+ current_item_ = item_iter_ != items_.end() ? item_iter_->get() : nullptr;
+}
+
+void NGInlineCursor::SetRoot(ItemsSpan items) {
+ DCHECK(items.data() || !items.size());
+ DCHECK_EQ(root_paint_fragment_, nullptr);
+ DCHECK_EQ(current_paint_fragment_, nullptr);
+ items_ = items;
+ MoveToItem(items_.begin());
+}
+
+void NGInlineCursor::SetRoot(const NGFragmentItems& items) {
+ SetRoot(items.Items());
+}
+
+void NGInlineCursor::SetRoot(const NGPaintFragment& root_paint_fragment) {
+ DCHECK(&root_paint_fragment);
+ root_paint_fragment_ = &root_paint_fragment;
+ current_paint_fragment_ = root_paint_fragment.FirstChild();
+}
+
+NGInlineCursor::NGInlineCursor(const LayoutBlockFlow& block_flow) {
+ DCHECK(&block_flow);
+
+ if (const NGPhysicalBoxFragment* fragment = block_flow.CurrentFragment()) {
+ if (const NGFragmentItems* items = fragment->Items()) {
+ SetRoot(*items);
+ return;
+ }
+ }
+
+ if (const NGPaintFragment* paint_fragment = block_flow.PaintFragment()) {
+ SetRoot(*paint_fragment);
+ return;
+ }
+
+ NOTREACHED();
+}
+
+NGInlineCursor::NGInlineCursor(const NGFragmentItems& items) {
+ SetRoot(items);
+}
+
+NGInlineCursor::NGInlineCursor(const NGPaintFragment& root_paint_fragment) {
+ SetRoot(root_paint_fragment);
+}
+
+bool NGInlineCursor::IsLineBox() const {
+ if (current_paint_fragment_)
+ return current_paint_fragment_->PhysicalFragment().IsLineBox();
+ if (current_item_)
+ return current_item_->Type() == NGFragmentItem::kLine;
+ NOTREACHED();
+ return false;
+}
+
+const NGPhysicalBoxFragment* NGInlineCursor::CurrentBoxFragment() const {
+ if (current_paint_fragment_) {
+ return DynamicTo<NGPhysicalBoxFragment>(
+ &current_paint_fragment_->PhysicalFragment());
+ }
+ if (current_item_)
+ return current_item_->BoxFragment();
+ NOTREACHED();
+ return nullptr;
+}
+
+const LayoutObject* NGInlineCursor::CurrentLayoutObject() const {
+ if (current_paint_fragment_)
+ return current_paint_fragment_->GetLayoutObject();
+ if (current_item_)
+ return current_item_->GetLayoutObject();
+ NOTREACHED();
+ return nullptr;
+}
+
+const PhysicalOffset NGInlineCursor::CurrentOffset() const {
+ if (current_paint_fragment_)
+ return current_paint_fragment_->InlineOffsetToContainerBox();
+ if (current_item_)
+ return current_item_->Offset();
+ NOTREACHED();
+ return PhysicalOffset();
+}
+
+void NGInlineCursor::MoveToNext() {
+ if (root_paint_fragment_) {
+ MoveToNextPaintFragment();
+ return;
+ }
+ MoveToNextItem();
+}
+
+void NGInlineCursor::MoveToNextSkippingChildren() {
+ if (root_paint_fragment_) {
+ MoveToNextPaintFragmentSkippingChildren();
+ return;
+ }
+ MoveToNextItemSkippingChildren();
+}
+
+void NGInlineCursor::MoveToNextItem() {
+ DCHECK((items_.data() || !items_.size()) && !root_paint_fragment_);
+ if (current_item_) {
+ DCHECK(item_iter_ != items_.end());
+ ++item_iter_;
+ current_item_ = item_iter_ != items_.end() ? item_iter_->get() : nullptr;
+ }
+}
+
+void NGInlineCursor::MoveToNextItemSkippingChildren() {
+ DCHECK((items_.data() || !items_.size()) && !root_paint_fragment_);
+ if (UNLIKELY(!current_item_))
+ return;
+ // If the current item has |ChildrenCount|, add it to move to the next
+ // sibling, skipping all children and their descendants.
+ if (wtf_size_t children_count = current_item_->ChildrenCount()) {
+ MoveToItem(item_iter_ + children_count);
+ return;
+ }
+ return MoveToNextItem();
+}
+
+void NGInlineCursor::MoveToParentPaintFragment() {
+ DCHECK(root_paint_fragment_ && current_paint_fragment_);
+ const NGPaintFragment* parent = current_paint_fragment_->Parent();
+ if (parent && parent != root_paint_fragment_) {
+ current_paint_fragment_ = parent;
+ return;
+ }
+ current_paint_fragment_ = nullptr;
+}
+
+void NGInlineCursor::MoveToNextPaintFragment() {
+ DCHECK(root_paint_fragment_ && current_paint_fragment_);
+ if (const NGPaintFragment* child = current_paint_fragment_->FirstChild()) {
+ current_paint_fragment_ = child;
+ return;
+ }
+ MoveToNextPaintFragmentSkippingChildren();
+}
+
+void NGInlineCursor::MoveToNextSibilingPaintFragment() {
+ DCHECK(root_paint_fragment_ && current_paint_fragment_);
+ if (const NGPaintFragment* next = current_paint_fragment_->NextSibling()) {
+ current_paint_fragment_ = next;
+ return;
+ }
+ current_paint_fragment_ = nullptr;
+}
+
+void NGInlineCursor::MoveToNextPaintFragmentSkippingChildren() {
+ DCHECK(root_paint_fragment_ && current_paint_fragment_);
+ while (!IsAtEnd()) {
+ if (const NGPaintFragment* next = current_paint_fragment_->NextSibling()) {
+ current_paint_fragment_ = next;
+ return;
+ }
+ MoveToParentPaintFragment();
+ }
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h
new file mode 100644
index 00000000000..6468d9d5a0a
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h
@@ -0,0 +1,98 @@
+// Copyright 2019 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_CURSOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_CURSOR_H_
+
+#include "base/containers/span.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
+
+namespace blink {
+
+class LayoutObject;
+class LayoutBlockFlow;
+class NGFragmentItem;
+class NGFragmentItems;
+class NGPaintFragment;
+class NGPhysicalBoxFragment;
+struct PhysicalOffset;
+
+// This class traverses fragments in an inline formatting context.
+//
+// When constructed, the initial position is empty. Call |MoveToNext()| to move
+// to the first fragment.
+//
+// TODO(kojii): |NGPaintFragment| should be gone when |NGPaintFragment| is
+// deprecated and all its uses are removed.
+class CORE_EXPORT NGInlineCursor {
+ STACK_ALLOCATED();
+
+ public:
+ NGInlineCursor(const LayoutBlockFlow& block_flow);
+ NGInlineCursor(const NGFragmentItems& items);
+ NGInlineCursor(const NGPaintFragment& root_paint_fragment);
+
+ //
+ // Functions to query the current position.
+ //
+
+ bool IsAtEnd() const { return !current_item_ && !current_paint_fragment_; }
+ explicit operator bool() const { return !IsAtEnd(); }
+
+ // True if the current position is a line box.
+ bool IsLineBox() const;
+
+ // |Current*| functions return an object for the current position.
+ const NGFragmentItem* CurrentItem() const { return current_item_; }
+ const NGPaintFragment* CurrentPaintFragment() const {
+ return current_paint_fragment_;
+ }
+ const NGPhysicalBoxFragment* CurrentBoxFragment() const;
+ const LayoutObject* CurrentLayoutObject() const;
+
+ // The offset relative to the root of the inline formatting context.
+ const PhysicalOffset CurrentOffset() const;
+
+ //
+ // Functions to move the current position.
+ //
+
+ // Move the current position to the next fragment in pre-order DFS. Returns
+ // |true| if the move was successful.
+ void MoveToNext();
+
+ // Same as |MoveToNext| except that this skips children even if they exist.
+ void MoveToNextSkippingChildren();
+
+ // TODO(kojii): Add more variations as needed, NextSibling,
+ // NextSkippingChildren, Previous, etc.
+
+ private:
+ using ItemsSpan = base::span<const std::unique_ptr<NGFragmentItem>>;
+
+ void SetRoot(const NGFragmentItems& items);
+ void SetRoot(ItemsSpan items);
+ void SetRoot(const NGPaintFragment& root_paint_fragment);
+
+ void MoveToItem(const ItemsSpan::iterator& iter);
+ void MoveToNextItem();
+ void MoveToNextItemSkippingChildren();
+
+ void MoveToParentPaintFragment();
+ void MoveToNextPaintFragment();
+ void MoveToNextSibilingPaintFragment();
+ void MoveToNextPaintFragmentSkippingChildren();
+
+ ItemsSpan items_;
+ ItemsSpan::iterator item_iter_;
+ const NGFragmentItem* current_item_ = nullptr;
+
+ const NGPaintFragment* root_paint_fragment_ = nullptr;
+ const NGPaintFragment* current_paint_fragment_ = nullptr;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_CURSOR_H_
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc
new file mode 100644
index 00000000000..dc23eb46ed4
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc
@@ -0,0 +1,128 @@
+// Copyright 2019 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_inline_cursor.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/layout/layout_text.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
+
+namespace blink {
+
+using ::testing::ElementsAre;
+
+class NGInlineCursorTest : public NGLayoutTest,
+ private ScopedLayoutNGFragmentItemForTest,
+ public testing::WithParamInterface<bool> {
+ public:
+ NGInlineCursorTest() : ScopedLayoutNGFragmentItemForTest(GetParam()) {}
+
+ protected:
+ Vector<String> ToDebugStringList(NGInlineCursor* cursor) {
+ Vector<String> list;
+ for (; *cursor; cursor->MoveToNext())
+ list.push_back(ToDebugString(*cursor));
+ return list;
+ }
+
+ String ToDebugString(const NGInlineCursor& cursor) {
+ if (const LayoutObject* layout_object = cursor.CurrentLayoutObject()) {
+ if (const LayoutText* text = ToLayoutTextOrNull(layout_object))
+ return text->GetText().StripWhiteSpace();
+
+ if (const Element* element =
+ DynamicTo<Element>(layout_object->GetNode())) {
+ if (const AtomicString& id = element->GetIdAttribute())
+ return "#" + id;
+ }
+
+ return layout_object->DebugName();
+ }
+
+ if (cursor.IsLineBox())
+ return "#linebox";
+ return "#null";
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(NGInlineCursorTest,
+ NGInlineCursorTest,
+ testing::Bool());
+
+TEST_P(NGInlineCursorTest, Next) {
+ SetBodyInnerHTML(R"HTML(
+ <style>
+ span { background: gray; }
+ </style>
+ <div id=root>
+ text1
+ <span id="span1">
+ text2
+ <span id="span2">
+ text3
+ </span>
+ text4
+ </span>
+ text5
+ </div>
+ )HTML");
+
+ LayoutBlockFlow* block_flow =
+ To<LayoutBlockFlow>(GetLayoutObjectByElementId("root"));
+ NGInlineCursor cursor(*block_flow);
+ Vector<String> list = ToDebugStringList(&cursor);
+ EXPECT_THAT(list, ElementsAre("#linebox", "text1", "#span1", "text2",
+ "#span2", "text3", "text4", "text5"));
+}
+
+TEST_P(NGInlineCursorTest, NextSkippingChildren) {
+ SetBodyInnerHTML(R"HTML(
+ <style>
+ span { background: gray; }
+ </style>
+ <div id=root>
+ text1
+ <span id="span1">
+ text2
+ <span id="span2">
+ text3
+ </span>
+ text4
+ </span>
+ text5
+ </div>
+ )HTML");
+
+ LayoutBlockFlow* block_flow =
+ To<LayoutBlockFlow>(GetLayoutObjectByElementId("root"));
+ NGInlineCursor cursor(*block_flow);
+ for (unsigned i = 0; i < 3; ++i)
+ cursor.MoveToNext();
+ EXPECT_EQ("text2", ToDebugString(cursor));
+ Vector<String> list;
+ while (true) {
+ cursor.MoveToNextSkippingChildren();
+ if (!cursor)
+ break;
+ list.push_back(ToDebugString(cursor));
+ }
+ EXPECT_THAT(list, ElementsAre("#span2", "text4", "text5"));
+}
+
+TEST_P(NGInlineCursorTest, EmptyOutOfFlow) {
+ SetBodyInnerHTML(R"HTML(
+ <div id=root>
+ <span style="position: absolute"></span>
+ </div>
+ )HTML");
+
+ LayoutBlockFlow* block_flow =
+ To<LayoutBlockFlow>(GetLayoutObjectByElementId("root"));
+ NGInlineCursor cursor(*block_flow);
+ Vector<String> list = ToDebugStringList(&cursor);
+ EXPECT_THAT(list, ElementsAre());
+}
+
+} // namespace blink
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 81be1dd387d..0ae66308706 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
@@ -124,6 +124,10 @@ class CORE_EXPORT NGInlineItem {
LayoutObject* GetLayoutObject() const { return layout_object_; }
+ bool IsImage() const {
+ return GetLayoutObject() && GetLayoutObject()->IsLayoutImage();
+ }
+
void SetOffset(unsigned start, unsigned end) {
DCHECK_GE(end, start);
start_offset_ = start;
@@ -250,6 +254,7 @@ class CORE_EXPORT NGInlineItem {
unsigned is_symbol_marker_ : 1;
unsigned is_generated_for_line_break_ : 1;
friend class NGInlineNode;
+ friend class NGInlineNodeDataEditor;
};
inline void NGInlineItem::AssertOffset(unsigned offset) const {
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 e73dbce4bd4..89f7f3474a7 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
@@ -1186,10 +1186,34 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::ExitInline(
LayoutObject* node) {
DCHECK(node);
- AppendOpaque(NGInlineItem::kCloseTag, node);
+ if (NeedsBoxInfo()) {
+ BoxInfo* current_box = &boxes_.back();
+ if (!current_box->should_create_box_fragment) {
+ // Set ShouldCreateBoxFragment if this inline box is empty so that we can
+ // compute its position/size correctly. Check this by looking for any
+ // non-empty items after the last |kOpenTag|.
+ const unsigned open_item_index = current_box->item_index;
+ DCHECK_GE(items_->size(), open_item_index + 1);
+ DCHECK_EQ((*items_)[open_item_index].Type(), NGInlineItem::kOpenTag);
+ for (unsigned i = items_->size() - 1;; --i) {
+ NGInlineItem& item = (*items_)[i];
+ if (i == open_item_index) {
+ DCHECK_EQ(i, current_box->item_index);
+ // TODO(kojii): <area> element fails to hit-test when we don't cull.
+ if (!IsHTMLAreaElement(item.GetLayoutObject()->GetNode()))
+ item.SetShouldCreateBoxFragment();
+ break;
+ }
+ DCHECK_GT(i, current_box->item_index);
+ if (!item.IsEmptyItem())
+ break;
+ }
+ }
- if (NeedsBoxInfo())
boxes_.pop_back();
+ }
+
+ AppendOpaque(NGInlineItem::kCloseTag, node);
Exit(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 d82beb0fb5d..39612fa1c01 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,7 +6,6 @@
#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"
@@ -295,7 +294,7 @@ void NGInlineLayoutAlgorithm::CreateLine(
if (UNLIKELY(Node().IsBidiEnabled())) {
box_states_->PrepareForReorder(&line_box_);
- BidiReorder();
+ BidiReorder(line_info->BaseDirection());
box_states_->UpdateAfterReorder(&line_box_);
}
LayoutUnit inline_size = box_states_->ComputeInlinePositions(&line_box_);
@@ -366,7 +365,6 @@ void NGInlineLayoutAlgorithm::CreateLine(
container_builder_.SetIsSelfCollapsing();
container_builder_.SetIsEmptyLineBox();
container_builder_.SetBaseDirection(line_info->BaseDirection());
- container_builder_.AddChildren(line_box_);
return;
}
@@ -380,7 +378,6 @@ void NGInlineLayoutAlgorithm::CreateLine(
if (line_info->UseFirstLineStyle())
container_builder_.SetStyleVariant(NGStyleVariant::kFirstLine);
container_builder_.SetBaseDirection(line_info->BaseDirection());
- container_builder_.AddChildren(line_box_);
container_builder_.SetInlineSize(inline_size);
container_builder_.SetMetrics(line_box_metrics);
container_builder_.SetBfcBlockOffset(line_info->BfcOffset().block_offset);
@@ -763,7 +760,7 @@ LayoutUnit NGInlineLayoutAlgorithm::ComputeContentSize(
NGBfcOffset bfc_offset = {ContainerBfcOffset().line_offset,
ContainerBfcOffset().block_offset + content_size};
AdjustToClearance(
- exclusion_space.ClearanceOffset(ResolvedClear(*item.Style(), Style())),
+ exclusion_space.ClearanceOffset(item.Style()->Clear(Style())),
&bfc_offset);
content_size = bfc_offset.block_offset - ContainerBfcOffset().block_offset;
}
@@ -958,6 +955,22 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
CHECK(is_line_created);
container_builder_.SetExclusionSpace(std::move(exclusion_space));
+
+ if (NGFragmentItemsBuilder* items_builder = context_->ItemsBuilder()) {
+ DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled());
+ container_builder_.AddOutOfFlowChildren(line_box_);
+ scoped_refptr<const NGLayoutResult> layout_result =
+ container_builder_.ToLineBoxFragment();
+ if (items_builder->TextContent(false).IsNull())
+ items_builder->SetTextContent(Node());
+ items_builder->SetCurrentLine(
+ To<NGPhysicalLineBoxFragment>(layout_result->PhysicalFragment()),
+ std::move(line_box_));
+ return layout_result;
+ }
+
+ DCHECK(!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled());
+ container_builder_.AddChildren(line_box_);
container_builder_.MoveOutOfFlowDescendantCandidatesToDescendants();
return container_builder_.ToLineBoxFragment();
}
@@ -968,7 +981,6 @@ unsigned NGInlineLayoutAlgorithm::PositionLeadingFloats(
NGExclusionSpace* exclusion_space,
NGPositionedFloatVector* positioned_floats) {
bool is_empty_inline = Node().IsEmptyInline();
- bool should_ignore_floats = BreakToken() && BreakToken()->IgnoreFloats();
const Vector<NGInlineItem>& items =
Node().ItemsData(/* is_first_line */ false).items;
@@ -983,12 +995,12 @@ unsigned NGInlineLayoutAlgorithm::PositionLeadingFloats(
break;
}
- if (item.Type() != NGInlineItem::kFloating || should_ignore_floats)
+ if (item.Type() != NGInlineItem::kFloating)
continue;
container_builder_.AddAdjoiningObjectTypes(
- ResolvedFloating(item.GetLayoutObject()->StyleRef().Floating(),
- ConstraintSpace().Direction()) == EFloat::kLeft
+ item.GetLayoutObject()->StyleRef().Floating(
+ ConstraintSpace().Direction()) == EFloat::kLeft
? kAdjoiningFloatLeft
: kAdjoiningFloatRight);
@@ -1022,37 +1034,75 @@ NGPositionedFloat NGInlineLayoutAlgorithm::PositionFloat(
&unpositioned_float, ConstraintSpace(), Style(), exclusion_space);
}
-void NGInlineLayoutAlgorithm::BidiReorder() {
+void NGInlineLayoutAlgorithm::BidiReorder(TextDirection base_direction) {
+ if (line_box_.IsEmpty())
+ return;
+
// TODO(kojii): UAX#9 L1 is not supported yet. Supporting L1 may change
// embedding levels of parts of runs, which requires to split items.
// http://unicode.org/reports/tr9/#L1
// BidiResolver does not support L1 crbug.com/316409.
+ // A sentinel value for items that are opaque to bidi reordering. Should be
+ // larger than the maximum resolved level.
+ constexpr UBiDiLevel kOpaqueBidiLevel = 0xff;
+ DCHECK_GT(kOpaqueBidiLevel, UBIDI_MAX_EXPLICIT_LEVEL + 1);
+
// Create a list of chunk indices in the visual order.
// ICU |ubidi_getVisualMap()| works for a run of characters. Since we can
// handle the direction of each run, we use |ubidi_reorderVisual()| to reorder
// runs instead of characters.
- NGLineBoxFragmentBuilder::ChildList logical_items;
Vector<UBiDiLevel, 32> levels;
- logical_items.ReserveInitialCapacity(line_box_.size());
levels.ReserveInitialCapacity(line_box_.size());
+ bool has_opaque_items = false;
for (NGLineBoxFragmentBuilder::Child& item : line_box_) {
- if (item.IsPlaceholder())
+ if (item.IsOpaqueToBidiReordering()) {
+ levels.push_back(kOpaqueBidiLevel);
+ has_opaque_items = true;
continue;
+ }
+ DCHECK_NE(item.bidi_level, kOpaqueBidiLevel);
levels.push_back(item.bidi_level);
- logical_items.AddChild(std::move(item));
- DCHECK(!item.HasInFlowFragment());
}
+ // For opaque items, copy bidi levels from adjacent items.
+ if (has_opaque_items) {
+ UBiDiLevel last_level = levels.front();
+ if (last_level == kOpaqueBidiLevel) {
+ for (const UBiDiLevel level : levels) {
+ if (level != kOpaqueBidiLevel) {
+ last_level = level;
+ break;
+ }
+ }
+ }
+ // If all items are opaque, use the base direction.
+ if (last_level == kOpaqueBidiLevel) {
+ if (IsLtr(base_direction))
+ return;
+ last_level = 1;
+ }
+ for (UBiDiLevel& level : levels) {
+ if (level == kOpaqueBidiLevel)
+ level = last_level;
+ else
+ last_level = level;
+ }
+ }
+
+ // Compute visual indices from resolved levels.
Vector<int32_t, 32> indices_in_visual_order(levels.size());
NGBidiParagraph::IndicesInVisualOrder(levels, &indices_in_visual_order);
// Reorder to the visual order.
- line_box_.resize(0);
+ NGLineBoxFragmentBuilder::ChildList visual_items;
+ visual_items.ReserveInitialCapacity(line_box_.size());
for (unsigned logical_index : indices_in_visual_order) {
- line_box_.AddChild(std::move(logical_items[logical_index]));
- DCHECK(!logical_items[logical_index].HasInFlowFragment());
+ visual_items.AddChild(std::move(line_box_[logical_index]));
+ DCHECK(!line_box_[logical_index].HasInFlowFragment());
}
+ DCHECK_EQ(line_box_.size(), visual_items.size());
+ line_box_ = std::move(visual_items);
}
} // namespace blink
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 d93ab75c097..4b7c45e4a14 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
@@ -74,7 +74,7 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final
const NGInlineItemResult&,
NGInlineBoxState*);
- void BidiReorder();
+ void BidiReorder(TextDirection base_direction);
void PlaceControlItem(const NGInlineItem&,
const NGLineInfo&,
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
index 94c4a3258ac..3c1aa36a1bc 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
@@ -43,12 +43,11 @@ TEST_F(NGInlineLayoutAlgorithmTest, BreakToken) {
NGInlineNode inline_node(block_flow);
LogicalSize size(LayoutUnit(50), LayoutUnit(20));
- NGConstraintSpace constraint_space =
- NGConstraintSpaceBuilder(
- WritingMode::kHorizontalTb, WritingMode::kHorizontalTb,
- /* is_new_fc */ false)
- .SetAvailableSize(size)
- .ToConstraintSpace();
+ NGConstraintSpaceBuilder builder(WritingMode::kHorizontalTb,
+ WritingMode::kHorizontalTb,
+ /* is_new_fc */ false);
+ builder.SetAvailableSize(size);
+ NGConstraintSpace constraint_space = builder.ToConstraintSpace();
NGInlineChildLayoutContext context;
scoped_refptr<const NGLayoutResult> layout_result =
@@ -126,6 +125,38 @@ TEST_F(NGInlineLayoutAlgorithmTest, GenerateEllipsis) {
EXPECT_EQ(line1.Children()[0]->GetLayoutObject(), ellipsis.GetLayoutObject());
}
+TEST_F(NGInlineLayoutAlgorithmTest, EllipsisInlineBoxOnly) {
+ LoadAhem();
+ SetBodyInnerHTML(R"HTML(
+ <!DOCTYPE html>
+ <style>
+ html, body { margin: 0; }
+ #container {
+ font: 10px/1 Ahem;
+ width: 5ch;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ span {
+ border: solid 10ch blue;
+ }
+ </style>
+ <div id=container><span></span></div>
+ )HTML");
+ scoped_refptr<const NGPhysicalBoxFragment> block =
+ GetBoxFragmentByElementId("container");
+ EXPECT_EQ(1u, block->Children().size());
+ const auto& line1 =
+ To<NGPhysicalLineBoxFragment>(*block->Children()[0].get());
+
+ // There should not be ellipsis in this line.
+ for (const auto& child : line1.Children()) {
+ if (const auto* text = DynamicTo<NGPhysicalTextFragment>(child.get())) {
+ EXPECT_FALSE(text->IsEllipsis());
+ }
+ }
+}
+
// 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.
@@ -226,8 +257,8 @@ TEST_F(NGInlineLayoutAlgorithmTest, ContainerBorderPadding) {
auto* block_flow =
To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"));
NGBlockNode block_node(block_flow);
- NGConstraintSpace space =
- NGConstraintSpace::CreateFromLayoutObject(*block_flow);
+ NGConstraintSpace space = NGConstraintSpace::CreateFromLayoutObject(
+ *block_flow, false /* is_layout_root */);
scoped_refptr<const NGLayoutResult> layout_result = block_node.Layout(space);
EXPECT_TRUE(layout_result->BfcBlockOffset().has_value());
@@ -260,8 +291,8 @@ TEST_F(NGInlineLayoutAlgorithmTest, MAYBE_VerticalAlignBottomReplaced) {
To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"));
NGInlineNode inline_node(block_flow);
NGInlineChildLayoutContext context;
- NGConstraintSpace space =
- NGConstraintSpace::CreateFromLayoutObject(*block_flow);
+ NGConstraintSpace space = NGConstraintSpace::CreateFromLayoutObject(
+ *block_flow, false /* is_layout_root */);
scoped_refptr<const NGLayoutResult> layout_result =
inline_node.Layout(space, nullptr, &context);
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 912fa83b133..e055e19d365 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
@@ -13,7 +13,6 @@
#include "third_party/blink/renderer/core/layout/layout_list_marker.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_dirty_lines.h"
@@ -40,6 +39,7 @@
#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"
+#include "third_party/blink/renderer/platform/wtf/text/string_buffer.h"
namespace blink {
@@ -132,6 +132,117 @@ class ItemsBuilderForMarkLineBoxesDirty {
bool has_floating_or_out_of_flow_positioned_ = false;
};
+// Wrapper over ShapeText that re-uses existing shape results for items that
+// haven't changed.
+class ReusingTextShaper final {
+ public:
+ ReusingTextShaper(NGInlineItemsData* data,
+ const Vector<NGInlineItem>* reusable_items)
+ : data_(*data),
+ reusable_items_(reusable_items),
+ shaper_(data->text_content) {}
+
+ scoped_refptr<ShapeResult> Shape(const NGInlineItem& start_item,
+ unsigned end_offset) {
+ const unsigned start_offset = start_item.StartOffset();
+ DCHECK_LT(start_offset, end_offset);
+
+ if (!reusable_items_)
+ return Reshape(start_item, start_offset, end_offset);
+
+ // TODO(yosin): We should support segment text
+ if (data_.segments)
+ return Reshape(start_item, start_offset, end_offset);
+
+ const Vector<const ShapeResult*> reusable_shape_results =
+ CollectReusableShapeResults(start_offset, end_offset,
+ start_item.Direction());
+ if (reusable_shape_results.IsEmpty())
+ return Reshape(start_item, start_offset, end_offset);
+
+ const scoped_refptr<ShapeResult> shape_result =
+ ShapeResult::CreateEmpty(*reusable_shape_results.front());
+ unsigned offset = start_offset;
+ for (const ShapeResult* reusable_shape_result : reusable_shape_results) {
+ DCHECK_LE(offset, reusable_shape_result->StartIndex());
+ if (offset < reusable_shape_result->StartIndex()) {
+ AppendShapeResult(
+ *Reshape(start_item, offset, reusable_shape_result->StartIndex()),
+ shape_result.get());
+ offset = shape_result->EndIndex();
+ }
+ DCHECK_EQ(offset, reusable_shape_result->StartIndex());
+ DCHECK(shape_result->NumCharacters() == 0 ||
+ shape_result->EndIndex() == offset);
+ reusable_shape_result->CopyRange(
+ offset, std::min(reusable_shape_result->EndIndex(), end_offset),
+ shape_result.get());
+ offset = shape_result->EndIndex();
+ if (offset == end_offset)
+ return shape_result;
+ }
+ DCHECK_LT(offset, end_offset);
+ AppendShapeResult(*Reshape(start_item, offset, end_offset),
+ shape_result.get());
+ return shape_result;
+ }
+
+ private:
+ void AppendShapeResult(const ShapeResult& shape_result, ShapeResult* target) {
+ DCHECK(target->NumCharacters() == 0 ||
+ target->EndIndex() == shape_result.StartIndex());
+ shape_result.CopyRange(shape_result.StartIndex(), shape_result.EndIndex(),
+ target);
+ }
+
+ Vector<const ShapeResult*> CollectReusableShapeResults(
+ unsigned start_offset,
+ unsigned end_offset,
+ TextDirection direction) {
+ DCHECK_LT(start_offset, end_offset);
+ Vector<const ShapeResult*> shape_results;
+ if (!reusable_items_)
+ return shape_results;
+ for (const NGInlineItem *item = std::lower_bound(
+ reusable_items_->begin(), reusable_items_->end(), start_offset,
+ [](const NGInlineItem&item, unsigned offset) {
+ return item.EndOffset() <= offset;
+ });
+ item != reusable_items_->end(); ++item) {
+ DCHECK_LE(start_offset, item->StartOffset());
+ if (end_offset <= item->StartOffset())
+ break;
+ if (item->EndOffset() < start_offset)
+ continue;
+ if (!item->TextShapeResult() || item->Direction() != direction)
+ continue;
+ shape_results.push_back(item->TextShapeResult());
+ }
+ return shape_results;
+ }
+
+ scoped_refptr<ShapeResult> Reshape(const NGInlineItem& start_item,
+ unsigned start_offset,
+ unsigned end_offset) {
+ DCHECK_LT(start_offset, end_offset);
+ const TextDirection direction = start_item.Direction();
+ const Font& font = start_item.Style()->GetFont();
+ if (data_.segments) {
+ return data_.segments->ShapeText(&shaper_, &font, direction, start_offset,
+ end_offset,
+ &start_item - data_.items.begin());
+ }
+ RunSegmenter::RunSegmenterRange range =
+ start_item.CreateRunSegmenterRange();
+ range.end = end_offset;
+ return shaper_.Shape(&font, direction, start_offset, end_offset, range);
+ }
+
+ NGInlineItemsData& data_;
+ const Vector<NGInlineItem>* const reusable_items_;
+ HarfBuzzShaper shaper_;
+};
+
// The function is templated to indicate the purpose of collected inlines:
// - With EmptyOffsetMappingBuilder: updating layout;
// - With NGOffsetMappingBuilder: building offset mapping on clean layout.
@@ -367,7 +478,7 @@ void NGInlineNode::PrepareLayout(
DCHECK(data);
CollectInlines(data, previous_data.get(), dirty_lines);
SegmentText(data);
- ShapeText(data, previous_data.get());
+ ShapeText(data, &previous_data->text_content);
ShapeTextForFirstLineIfNeeded(data);
AssociateItemsWithInlines(data);
DCHECK_EQ(data, MutableData());
@@ -385,6 +496,288 @@ void NGInlineNode::PrepareLayout(
#endif
}
+// Building |NGInlineNodeData| for |LayoutText::SetTextWithOffset()| with
+// reusing data.
+class NGInlineNodeDataEditor final {
+ STACK_ALLOCATED();
+
+ public:
+ explicit NGInlineNodeDataEditor(const LayoutText& layout_text)
+ : block_flow_(layout_text.ContainingNGBlockFlow()),
+ layout_text_(layout_text) {
+ DCHECK(layout_text_.HasValidInlineItems());
+ }
+
+ LayoutBlockFlow* GetLayoutBlockFlow() const { return block_flow_; }
+
+ // Note: We can't use |Position| for |layout_text_.GetNode()| because |Text|
+ // node is already changed.
+ NGInlineNodeData* Prepare(unsigned offset, unsigned length) {
+ if (!block_flow_ || block_flow_->NeedsCollectInlines() ||
+ block_flow_->NeedsLayout() ||
+ block_flow_->GetDocument().NeedsLayoutTreeUpdate() ||
+ !block_flow_->GetNGInlineNodeData() ||
+ block_flow_->GetNGInlineNodeData()->text_content.IsNull())
+ return nullptr;
+
+ // Because of current text content has secured text, e.g. whole text is
+ // "***", all characters including collapsed white spaces are marker, and
+ // new text is original text, we can't reuse shape result.
+ if (layout_text_.StyleRef().TextSecurity() != ETextSecurity::kNone)
+ return nullptr;
+
+ // Note: We should compute offset mapping before calling
+ // |LayoutBlockFlow::TakeNGInlineNodeData()|
+ const NGOffsetMapping* const offset_mapping =
+ NGInlineNode::GetOffsetMapping(block_flow_);
+ DCHECK(offset_mapping);
+ const auto units =
+ offset_mapping->GetMappingUnitsForLayoutObject(layout_text_);
+ start_offset_ = ConvertDOMOffsetToTextContent(units, offset);
+ end_offset_ = ConvertDOMOffsetToTextContent(units, offset + length);
+ DCHECK_LE(start_offset_, end_offset_);
+ data_.reset(block_flow_->TakeNGInlineNodeData());
+ return data_.get();
+ }
+
+ void Run() {
+ const NGInlineNodeData& new_data = *block_flow_->GetNGInlineNodeData();
+ const int diff =
+ new_data.text_content.length() - data_->text_content.length();
+ // |inserted_text_length| can be negative when white space is collapsed
+ // after text change.
+ // * "ab cd ef" => delete "cd" => "ab ef"
+ // We should not reuse " " before "ef"
+ // * "a bc" => delete "bc" => "a"
+ // There are no spaces after "a".
+ const int inserted_text_length = end_offset_ - start_offset_ + diff;
+ DCHECK_GE(inserted_text_length, -1);
+ const unsigned start_offset =
+ inserted_text_length < 0 && end_offset_ == data_->text_content.length()
+ ? start_offset_ - 1
+ : start_offset_;
+ const unsigned end_offset =
+ inserted_text_length < 0 && start_offset_ == start_offset
+ ? end_offset_ + 1
+ : end_offset_;
+ DCHECK_LE(end_offset, data_->text_content.length());
+ DCHECK_LE(start_offset, end_offset);
+#if DCHECK_IS_ON()
+ if (start_offset_ != start_offset) {
+ DCHECK_EQ(data_->text_content[start_offset], ' ');
+ DCHECK_EQ(end_offset, end_offset_);
+ }
+ if (end_offset_ != end_offset) {
+ DCHECK_EQ(data_->text_content[end_offset_], ' ');
+ DCHECK_EQ(start_offset, start_offset_);
+ }
+#endif
+ Vector<NGInlineItem> items;
+ // +3 for before and after replaced text.
+ items.ReserveInitialCapacity(data_->items.size() + 3);
+
+ // Copy items before replaced range
+ auto* it = data_->items.begin();
+ while (it->end_offset_ < start_offset ||
+ it->layout_object_ != layout_text_) {
+ DCHECK(it != data_->items.end());
+ items.push_back(*it);
+ ++it;
+ }
+
+ DCHECK_EQ(it->layout_object_, layout_text_);
+
+ // Copy part of item before replaced range.
+ if (it->start_offset_ < start_offset)
+ items.push_back(CopyItemBefore(*it, start_offset));
+
+ // Skip items in replaced range.
+ while (it->end_offset_ < end_offset)
+ ++it;
+ DCHECK_EQ(it->layout_object_, layout_text_);
+
+ // Inserted text
+ if (inserted_text_length > 0) {
+ const unsigned inserted_start_offset =
+ items.IsEmpty() ? 0 : items.back().end_offset_;
+ const unsigned inserted_end_offset =
+ inserted_start_offset + inserted_text_length;
+ items.push_back(NGInlineItem(*it, inserted_start_offset,
+ inserted_end_offset, nullptr));
+ }
+
+ // Copy part of item after replaced range.
+ if (end_offset < it->end_offset_) {
+ items.push_back(CopyItemAfter(*it, end_offset));
+ ShiftItem(&items.back(), diff);
+ }
+
+ // Copy items after replaced range
+ ++it;
+ while (it != data_->items.end()) {
+ DCHECK_NE(it->layout_object_, layout_text_);
+ DCHECK_LE(end_offset, it->start_offset_);
+ items.push_back(*it);
+ ShiftItem(&items.back(), diff);
+ ++it;
+ }
+
+ VerifyItems(items);
+ data_->items = std::move(items);
+ data_->text_content = new_data.text_content;
+ }
+
+ private:
+ static unsigned AdjustOffset(unsigned offset, int delta) {
+ if (delta > 0)
+ return offset + delta;
+ return offset - (-delta);
+ }
+
+ static unsigned ConvertDOMOffsetToTextContent(
+ base::span<const NGOffsetMappingUnit> units,
+ unsigned offset) {
+ auto it = std::find_if(
+ units.begin(), units.end(), [offset](const NGOffsetMappingUnit& unit) {
+ return unit.DOMStart() <= offset && offset <= unit.DOMEnd();
+ });
+ DCHECK(it != units.end());
+ return it->ConvertDOMOffsetToTextContent(offset);
+ }
+
+ // Returns copy of |item| after |start_offset| (inclusive).
+ NGInlineItem CopyItemAfter(const NGInlineItem& item,
+ unsigned start_offset) const {
+ DCHECK_LE(item.start_offset_, start_offset);
+ DCHECK_LT(start_offset, item.end_offset_);
+ DCHECK_EQ(item.layout_object_, layout_text_);
+ if (item.start_offset_ == start_offset)
+ return item;
+ const unsigned end_offset = item.end_offset_;
+ if (!item.shape_result_)
+ return NGInlineItem(item, start_offset, end_offset, nullptr);
+ // TODO(yosin): We should handle |shape_result| doesn't have safe-to-break
+ // at start and end, because of |ShapeText()| splits |ShapeResult| ignoring
+ // safe-to-break offset.
+ item.shape_result_->EnsurePositionData();
+ const unsigned safe_start_offset =
+ item.shape_result_->CachedNextSafeToBreakOffset(start_offset);
+ if (end_offset == safe_start_offset)
+ return NGInlineItem(item, start_offset, end_offset, nullptr);
+ return NGInlineItem(
+ item, start_offset, end_offset,
+ item.shape_result_->SubRange(safe_start_offset, end_offset));
+ }
+
+ // Returns copy of |item| before |end_offset| (exclusive).
+ NGInlineItem CopyItemBefore(const NGInlineItem& item,
+ unsigned end_offset) const {
+ DCHECK_LT(item.start_offset_, end_offset);
+ DCHECK_LE(end_offset, item.end_offset_);
+ DCHECK_EQ(item.layout_object_, layout_text_);
+ if (item.end_offset_ == end_offset)
+ return item;
+ const unsigned start_offset = item.start_offset_;
+ if (!item.shape_result_)
+ return NGInlineItem(item, start_offset, end_offset, nullptr);
+ // TODO(yosin): We should handle |shape_result| doesn't have safe-to-break
+ // at start and end, because of |ShapeText()| splits |ShapeResult| ignoring
+ // safe-to-break offset.
+ item.shape_result_->EnsurePositionData();
+ const unsigned safe_end_offset =
+ item.shape_result_->CachedPreviousSafeToBreakOffset(end_offset);
+ if (start_offset == safe_end_offset)
+ return NGInlineItem(item, start_offset, end_offset, nullptr);
+ return NGInlineItem(
+ item, start_offset, end_offset,
+ item.shape_result_->SubRange(start_offset, safe_end_offset));
+ }
+
+ static void ShiftItem(NGInlineItem* item, int delta) {
+ if (delta == 0)
+ return;
+ item->start_offset_ = AdjustOffset(item->start_offset_, delta);
+ item->end_offset_ = AdjustOffset(item->end_offset_, delta);
+ if (!item->shape_result_)
+ return;
+ item->shape_result_ =
+ item->shape_result_->CopyAdjustedOffset(item->start_offset_);
+ }
+
+ void VerifyItems(const Vector<NGInlineItem>& items) const {
+#if DCHECK_IS_ON()
+ unsigned last_offset = items.front().start_offset_;
+ for (const NGInlineItem& item : items) {
+ DCHECK_LE(item.start_offset_, item.end_offset_);
+ DCHECK_EQ(last_offset, item.start_offset_);
+ last_offset = item.end_offset_;
+ if (!item.shape_result_ || item.layout_object_ != layout_text_)
+ continue;
+ DCHECK_LT(item.start_offset_, item.end_offset_);
+ if (item.shape_result_->StartIndex() == item.start_offset_) {
+ DCHECK_LE(item.shape_result_->EndIndex(), item.end_offset_);
+ } else {
+ DCHECK_LE(item.start_offset_, item.shape_result_->StartIndex());
+ DCHECK_EQ(item.end_offset_, item.shape_result_->EndIndex());
+ }
+ }
+ DCHECK_EQ(last_offset,
+ block_flow_->GetNGInlineNodeData()->text_content.length());
+#endif
+ }
+
+ std::unique_ptr<NGInlineNodeData> data_;
+ LayoutBlockFlow* const block_flow_;
+ const LayoutText& layout_text_;
+ unsigned start_offset_ = 0;
+ unsigned end_offset_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(NGInlineNodeDataEditor);
+};
+
+// static
+bool NGInlineNode::SetTextWithOffset(LayoutText* layout_text,
+ scoped_refptr<StringImpl> new_text_in,
+ unsigned offset,
+ unsigned length) {
+ if (!layout_text->HasValidInlineItems() ||
+ !layout_text->IsInLayoutNGInlineFormattingContext())
+ return false;
+ const String old_text = layout_text->GetText();
+ if (offset == 0 && length == old_text.length()) {
+ // We'll run collect inline items since whole text of |layout_text| is
+ // changed.
+ return false;
+ }
+
+ NGInlineNodeDataEditor editor(*layout_text);
+ NGInlineNodeData* const previous_data = editor.Prepare(offset, length);
+ if (!previous_data)
+ return false;
+
+ String new_text(std::move(new_text_in));
+ layout_text->StyleRef().ApplyTextTransform(&new_text,
+ layout_text->PreviousCharacter());
+ layout_text->SetTextInternal(new_text.Impl());
+
+ NGInlineNode node(editor.GetLayoutBlockFlow());
+ NGInlineNodeData* data = node.MutableData();
+ data->items.ReserveCapacity(previous_data->items.size());
+ NGInlineItemsBuilder builder(&data->items, nullptr);
+ // TODO(yosin): We should reuse before/after |layout_text| during collecting
+ // inline items.
+ layout_text->ClearInlineItems();
+ CollectInlinesInternal(node.GetLayoutBlockFlow(), &builder, previous_data);
+ data->text_content = builder.ToString();
+ // Relocates |ShapeResult| in |previous_data| after |offset|+|length|
+ editor.Run();
+ node.SegmentText(data);
+ node.ShapeText(data, &previous_data->text_content, &previous_data->items);
+ node.ShapeTextForFirstLineIfNeeded(data);
+ node.AssociateItemsWithInlines(data);
+ return true;
+}
+
const NGInlineNodeData& NGInlineNode::EnsureData() {
PrepareLayoutIfNeeded();
return Data();
@@ -559,22 +952,26 @@ void NGInlineNode::SegmentFontOrientation(NGInlineNodeData* data) {
// If we don't have |NGInlineItemSegments| yet, create a segment for the
// entire content.
const unsigned capacity = items.size() + text_content.length() / 10;
- if (!data->segments) {
- data->segments = std::make_unique<NGInlineItemSegments>();
- data->segments->ReserveCapacity(capacity);
- data->segments->Append(text_content.length(), items.front());
- } else {
+ NGInlineItemSegments* segments = data->segments.get();
+ if (segments) {
DCHECK(!data->segments->IsEmpty());
data->segments->ReserveCapacity(capacity);
+ DCHECK_EQ(text_content.length(), data->segments->EndOffset());
}
- DCHECK_EQ(text_content.length(), data->segments->EndOffset());
unsigned segment_index = 0;
for (const NGInlineItem& item : items) {
if (item.Type() == NGInlineItem::kText && item.Length() &&
item.Style()->GetFont().GetFontDescription().Orientation() ==
FontOrientation::kVerticalMixed) {
- segment_index = data->segments->AppendMixedFontOrientation(
+ if (!segments) {
+ data->segments = std::make_unique<NGInlineItemSegments>();
+ segments = data->segments.get();
+ segments->ReserveCapacity(capacity);
+ segments->Append(text_content.length(), item);
+ DCHECK_EQ(text_content.length(), data->segments->EndOffset());
+ }
+ segment_index = segments->AppendMixedFontOrientation(
text_content, item.StartOffset(), item.EndOffset(), segment_index);
}
}
@@ -626,14 +1023,13 @@ void NGInlineNode::SegmentBidiRuns(NGInlineNodeData* data) {
}
void NGInlineNode::ShapeText(NGInlineItemsData* data,
- NGInlineItemsData* previous_data) {
+ const String* previous_text,
+ const Vector<NGInlineItem>* previous_items) {
const String& text_content = data->text_content;
Vector<NGInlineItem>* items = &data->items;
- const String* previous_text =
- previous_data ? &previous_data->text_content : nullptr;
// Provide full context of the entire node to the shaper.
- HarfBuzzShaper shaper(text_content);
+ ReusingTextShaper shaper(data, previous_items);
ShapeResultSpacing<String> spacing(text_content);
DCHECK(!data->segments ||
@@ -718,43 +1114,35 @@ void NGInlineNode::ShapeText(NGInlineItemsData* data,
}
// Results may only be reused if all items in the range remain valid.
- bool has_valid_shape_results = true;
- for (unsigned item_index = index; item_index < end_index; item_index++) {
- if (NeedsShaping((*items)[item_index])) {
- has_valid_shape_results = false;
- break;
+ if (previous_text) {
+ bool has_valid_shape_results = true;
+ for (unsigned item_index = index; item_index < end_index; item_index++) {
+ if (NeedsShaping((*items)[item_index])) {
+ has_valid_shape_results = false;
+ break;
+ }
}
- }
- // When shaping across multiple items checking whether the individual
- // items has valid shape results isn't sufficient as items may have been
- // re-ordered or removed.
- // TODO(layout-dev): It would probably be faster to check for removed or
- // moved items but for now comparing the string itself will do.
- unsigned text_start = start_item.StartOffset();
- DCHECK_GE(end_offset, text_start);
- unsigned text_length = end_offset - text_start;
- if (has_valid_shape_results && previous_text &&
- end_offset <= previous_text->length() &&
- StringView(text_content, text_start, text_length) ==
- StringView(*previous_text, text_start, text_length)) {
- index = end_index;
- continue;
+ // When shaping across multiple items checking whether the individual
+ // items has valid shape results isn't sufficient as items may have been
+ // re-ordered or removed.
+ // TODO(layout-dev): It would probably be faster to check for removed or
+ // moved items but for now comparing the string itself will do.
+ unsigned text_start = start_item.StartOffset();
+ DCHECK_GE(end_offset, text_start);
+ unsigned text_length = end_offset - text_start;
+ if (has_valid_shape_results && previous_text &&
+ end_offset <= previous_text->length() &&
+ StringView(text_content, text_start, text_length) ==
+ StringView(*previous_text, text_start, text_length)) {
+ index = end_index;
+ continue;
+ }
}
// Shape each item with the full context of the entire node.
- scoped_refptr<ShapeResult> shape_result;
- if (!data->segments) {
- RunSegmenter::RunSegmenterRange range =
- start_item.CreateRunSegmenterRange();
- range.end = end_offset;
- shape_result = shaper.Shape(&font, direction, start_item.StartOffset(),
- end_offset, range);
- } else {
- shape_result = data->segments->ShapeText(
- &shaper, &font, direction, start_item.StartOffset(), end_offset,
- &start_item - items->begin());
- }
+ scoped_refptr<ShapeResult> shape_result =
+ shaper.Shape(start_item, end_offset);
if (UNLIKELY(spacing.SetSpacing(font.GetFontDescription())))
shape_result->ApplySpacing(spacing);
@@ -1044,6 +1432,50 @@ bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow,
return !builder.ShouldAbort();
}
+namespace {
+
+template <typename CharType>
+scoped_refptr<StringImpl> CreateTextContentForStickyImagesQuirk(
+ const CharType* text,
+ unsigned length,
+ base::span<const NGInlineItem> items) {
+ StringBuffer<CharType> buffer(length);
+ CharType* characters = buffer.Characters();
+ memcpy(characters, text, length * sizeof(CharType));
+ for (const NGInlineItem& item : items) {
+ if (item.Type() == NGInlineItem::kAtomicInline && item.IsImage()) {
+ DCHECK_EQ(characters[item.StartOffset()], kObjectReplacementCharacter);
+ characters[item.StartOffset()] = kNoBreakSpaceCharacter;
+ }
+ }
+ return buffer.Release();
+}
+
+} // namespace
+
+// The stick images quirk changes the line breaking behavior around images. This
+// function returns a text content that has non-breaking spaces for images, so
+// that no changes are needed in the line breaking logic.
+// https://quirks.spec.whatwg.org/#the-table-cell-width-calculation-quirk
+// static
+String NGInlineNode::TextContentForStickyImagesQuirk(
+ const NGInlineItemsData& items_data) {
+ const String& text_content = items_data.text_content;
+ for (const NGInlineItem& item : items_data.items) {
+ if (item.Type() == NGInlineItem::kAtomicInline && item.IsImage()) {
+ if (text_content.Is8Bit()) {
+ return CreateTextContentForStickyImagesQuirk(
+ text_content.Characters8(), text_content.length(),
+ base::span<const NGInlineItem>(&item, items_data.items.end()));
+ }
+ return CreateTextContentForStickyImagesQuirk(
+ text_content.Characters16(), text_content.length(),
+ base::span<const NGInlineItem>(&item, items_data.items.end()));
+ }
+ }
+ return text_content;
+}
+
static LayoutUnit ComputeContentSize(
NGInlineNode node,
WritingMode container_writing_mode,
@@ -1056,16 +1488,15 @@ static LayoutUnit ComputeContentSize(
LayoutUnit available_inline_size =
mode == NGLineBreakerMode::kMaxContent ? LayoutUnit::Max() : LayoutUnit();
- NGConstraintSpace space =
- NGConstraintSpaceBuilder(/* parent_writing_mode */ writing_mode,
- /* out_writing_mode */ writing_mode,
- /* is_new_fc */ false)
- .SetTextDirection(style.Direction())
- .SetAvailableSize({available_inline_size, kIndefiniteSize})
- .SetPercentageResolutionSize({LayoutUnit(), LayoutUnit()})
- .SetReplacedPercentageResolutionSize({LayoutUnit(), LayoutUnit()})
- .SetIsIntermediateLayout(true)
- .ToConstraintSpace();
+ NGConstraintSpaceBuilder builder(/* parent_writing_mode */ writing_mode,
+ /* out_writing_mode */ writing_mode,
+ /* is_new_fc */ false);
+ builder.SetTextDirection(style.Direction());
+ builder.SetAvailableSize({available_inline_size, kIndefiniteSize});
+ builder.SetPercentageResolutionSize({LayoutUnit(), LayoutUnit()});
+ builder.SetReplacedPercentageResolutionSize({LayoutUnit(), LayoutUnit()});
+ builder.SetIsIntermediateLayout(true);
+ NGConstraintSpace space = builder.ToConstraintSpace();
NGExclusionSpace empty_exclusion_space;
NGPositionedFloatVector empty_leading_floats;
@@ -1104,7 +1535,7 @@ static LayoutUnit ComputeContentSize(
EFloat previous_float_type = EFloat::kNone;
for (const auto& floating_object : floating_objects_) {
const EClear float_clear =
- ResolvedClear(floating_object.float_style, floating_object.style);
+ floating_object.float_style.Clear(floating_object.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
@@ -1122,8 +1553,8 @@ static LayoutUnit ComputeContentSize(
// such float should not affect the content size.
floats_inline_size_ += floating_object.float_inline_max_size_with_margin
.ClampNegativeToZero();
- previous_float_type = ResolvedFloating(floating_object.float_style,
- floating_object.style);
+ previous_float_type =
+ floating_object.float_style.Floating(floating_object.style);
}
max_inline_size =
std::max(max_inline_size, line_inline_size + floats_inline_size_);
@@ -1179,11 +1610,11 @@ static LayoutUnit ComputeContentSize(
}
}
- void ForceLineBreak(const NGInlineItem& item) {
- // Add all text up to the forced break. There may be spaces that were
+ void ForceLineBreak(const NGLineInfo& line_info) {
+ // Add all text up to the end of the line. There may be spaces that were
// removed during the line breaking.
- AddTextUntil(&item);
- next_item = std::next(&item);
+ CHECK_LE(line_info.EndItemIndex(), items_data.items.size());
+ AddTextUntil(items_data.items.begin() + line_info.EndItemIndex());
max_size = floats->ComputeMaxSizeForLine(position.ClampNegativeToZero(),
max_size);
position = LayoutUnit();
@@ -1217,6 +1648,7 @@ static LayoutUnit ComputeContentSize(
is_after_break = false;
}
+ bool has_forced_break = false;
for (const NGInlineItemResult& result : line_info.Results()) {
const NGInlineItem& item = *result.item;
if (item.Type() == NGInlineItem::kText) {
@@ -1234,7 +1666,11 @@ static LayoutUnit ComputeContentSize(
if (item.Type() == NGInlineItem::kControl) {
UChar c = items_data.text_content[item.StartOffset()];
if (c == kNewlineCharacter) {
- ForceLineBreak(item);
+ // Compute the forced break after all results were handled, because
+ // when close tags appear after a forced break, they are included in
+ // the line, and they may have inline sizes. crbug.com/991320.
+ DCHECK(!has_forced_break);
+ has_forced_break = true;
continue;
}
// Tabulation characters change the widths by their positions, so
@@ -1247,6 +1683,8 @@ static LayoutUnit ComputeContentSize(
}
position += result.inline_size;
}
+ if (has_forced_break)
+ ForceLineBreak(line_info);
}
};
FloatsMaxSize floats_max_size(input);
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 e9f6f1031aa..318a6cefa28 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
@@ -72,12 +72,26 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
return Data().ItemsData(is_first_line);
}
+ // Returns the text content to use for content sizing. This is normally the
+ // same as |items_data.text_content|, except when sticky images quirk is
+ // needed.
+ String TextContentForContentSize(const NGInlineItemsData& items_data) const;
+
// Clear associated fragments for LayoutObjects.
// They are associated when NGPaintFragment is constructed, but when clearing,
// NGInlineItem provides easier and faster logic.
static void ClearAssociatedFragments(const NGPhysicalFragment& fragment,
const NGBlockBreakToken* break_token);
+ // Returns true if we don't need to collect inline items after replacing
+ // |layout_text| after deleting replacing subtext from |offset| to |length|
+ // |new_text| is new text of |layout_text|.
+ // This is optimized version of |PrepareLayout()|.
+ static bool SetTextWithOffset(LayoutText* layout_text,
+ scoped_refptr<StringImpl> new_text,
+ unsigned offset,
+ unsigned length);
+
// Returns the DOM to text content offset mapping of this block. If it is not
// computed before, compute and store it in NGInlineNodeData.
// This funciton must be called with clean layout.
@@ -124,7 +138,8 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
void SegmentFontOrientation(NGInlineNodeData*);
void SegmentBidiRuns(NGInlineNodeData*);
void ShapeText(NGInlineItemsData*,
- NGInlineItemsData* previous_data = nullptr);
+ const String* previous_text = nullptr,
+ const Vector<NGInlineItem>* previous_items = nullptr);
void ShapeTextForFirstLineIfNeeded(NGInlineNodeData*);
void AssociateItemsWithInlines(NGInlineNodeData*);
@@ -145,6 +160,8 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
}
const NGInlineNodeData& EnsureData();
+ static String TextContentForStickyImagesQuirk(const NGInlineItemsData&);
+
static void ComputeOffsetMapping(LayoutBlockFlow* layout_block_flow,
NGInlineNodeData* data);
@@ -152,6 +169,30 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
friend class NGInlineNodeLegacy;
};
+inline String NGInlineNode::TextContentForContentSize(
+ const NGInlineItemsData& items_data) const {
+ const String& text_content = items_data.text_content;
+ if (UNLIKELY(text_content.IsEmpty()))
+ return text_content;
+
+ // There's a special intrinsic size measure quirk for images that are direct
+ // children of table cells that have auto inline-size: When measuring
+ // intrinsic min/max inline sizes, we pretend that it's not possible to break
+ // between images, or between text and images. Note that this only applies
+ // when measuring. During actual layout, on the other hand, standard breaking
+ // rules are to be followed.
+ // See https://quirks.spec.whatwg.org/#the-table-cell-width-calculation-quirk
+ if (UNLIKELY(GetDocument().InQuirksMode())) {
+ const ComputedStyle& style = Style();
+ if (UNLIKELY(style.Display() == EDisplay::kTableCell &&
+ style.LogicalWidth().IsIntrinsicOrAuto())) {
+ return TextContentForStickyImagesQuirk(items_data);
+ }
+ }
+
+ return text_content;
+}
+
template <>
struct DowncastTraits<NGInlineNode> {
static bool AllowFrom(const NGLayoutInputNode& node) {
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 2e844d26617..2d316800b0c 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
@@ -124,12 +124,11 @@ class NGInlineNodeTest : public NGLayoutTest {
void CreateLine(
NGInlineNode node,
Vector<scoped_refptr<const NGPhysicalTextFragment>>* fragments_out) {
- NGConstraintSpace constraint_space =
- NGConstraintSpaceBuilder(WritingMode::kHorizontalTb,
- WritingMode::kHorizontalTb,
- /* is_new_fc */ false)
- .SetAvailableSize({LayoutUnit::Max(), LayoutUnit(-1)})
- .ToConstraintSpace();
+ NGConstraintSpaceBuilder builder(WritingMode::kHorizontalTb,
+ WritingMode::kHorizontalTb,
+ /* is_new_fc */ false);
+ builder.SetAvailableSize({LayoutUnit::Max(), LayoutUnit(-1)});
+ NGConstraintSpace constraint_space = builder.ToConstraintSpace();
NGInlineChildLayoutContext context;
scoped_refptr<const NGLayoutResult> result =
NGInlineLayoutAlgorithm(node, constraint_space,
@@ -503,6 +502,25 @@ TEST_F(NGInlineNodeTest, MinMaxSizeFloats) {
EXPECT_EQ(130, sizes.max_size);
}
+TEST_F(NGInlineNodeTest, MinMaxSizeCloseTagAfterForcedBreak) {
+ LoadAhem();
+ SetupHtml("t", R"HTML(
+ <style>
+ span { border: 30px solid blue; }
+ </style>
+ <div id=t style="font: 10px Ahem">
+ <span>12<br></span>
+ </div>
+ )HTML");
+
+ NGInlineNodeForTest node = CreateInlineNode();
+ MinMaxSize sizes = ComputeMinMaxSize(node);
+ // The right border of the `</span>` is included in the line even if it
+ // appears after `<br>`. crbug.com/991320.
+ EXPECT_EQ(80, sizes.min_size);
+ EXPECT_EQ(80, sizes.max_size);
+}
+
TEST_F(NGInlineNodeTest, MinMaxSizeFloatsClearance) {
LoadAhem();
SetupHtml("t", R"HTML(
@@ -923,16 +941,7 @@ TEST_F(NGInlineNodeTest, InvalidateSetText) {
EXPECT_FALSE(layout_block_flow_->NeedsCollectInlines());
LayoutText* text = ToLayoutText(layout_block_flow_->FirstChild());
- text->SetText(String("after").Impl());
- EXPECT_TRUE(layout_block_flow_->NeedsCollectInlines());
-}
-
-TEST_F(NGInlineNodeTest, InvalidateSetTextWithOffset) {
- SetupHtml("t", "<div id=t>before</div>");
- EXPECT_FALSE(layout_block_flow_->NeedsCollectInlines());
-
- LayoutText* text = ToLayoutText(layout_block_flow_->FirstChild());
- text->SetTextWithOffset(String("after").Impl(), 1, 4);
+ text->SetTextIfNeeded(String("after").Impl());
EXPECT_TRUE(layout_block_flow_->NeedsCollectInlines());
}
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 55cb128a930..206b6163dbc 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
@@ -108,6 +108,20 @@ void NGLineBoxFragmentBuilder::AddChildren(ChildList& children) {
}
}
+void NGLineBoxFragmentBuilder::AddOutOfFlowChildren(ChildList& children) {
+ for (auto& child : children) {
+ if (child.out_of_flow_positioned_box) {
+ AddOutOfFlowChildCandidate(
+ NGBlockNode(ToLayoutBox(child.out_of_flow_positioned_box)),
+ child.offset, child.container_direction);
+ child.out_of_flow_positioned_box = nullptr;
+ }
+ }
+
+ DCHECK(oof_positioned_descendants_.IsEmpty());
+ MoveOutOfFlowDescendantCandidatesToDescendants();
+}
+
scoped_refptr<const NGLayoutResult>
NGLineBoxFragmentBuilder::ToLineBoxFragment() {
writing_mode_ = ToLineWritingMode(writing_mode_);
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 7142082a860..80aaf47eba1 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
@@ -91,6 +91,10 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
// The index of |box_data_list_|, used in |PrepareForReorder()| and
// |UpdateAfterReorder()| to track children of boxes across BiDi reorder.
unsigned box_data_index = 0;
+ // For an inline box, shows the number of descendant |Child|ren, including
+ // empty ones. Includes itself, so 1 means no descendants. 0 if not an
+ // inline box. Available only after |CreateBoxFragments()|.
+ unsigned children_count = 0;
UBiDiLevel bidi_level = 0xff;
// The current text direction for OOF positioned items.
TextDirection container_direction = TextDirection::kLtr;
@@ -162,6 +166,20 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
}
bool HasBidiLevel() const { return bidi_level != 0xff; }
bool IsPlaceholder() const { return !HasFragment() && !HasBidiLevel(); }
+ bool IsOpaqueToBidiReordering() const {
+ if (IsPlaceholder())
+ return true;
+ // Skip all inline boxes. Fragments for inline boxes maybe created earlier
+ // if they have no children.
+ if (layout_result) {
+ const LayoutObject* layout_object =
+ layout_result->PhysicalFragment().GetLayoutObject();
+ DCHECK(layout_object);
+ if (layout_object->IsLayoutInline())
+ return true;
+ }
+ return false;
+ }
const NGPhysicalFragment* PhysicalFragment() const {
if (layout_result)
return &layout_result->PhysicalFragment();
@@ -177,7 +195,7 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
public:
ChildList() = default;
- void operator=(ChildList&& other) {
+ void operator=(ChildList&& other) noexcept {
children_ = std::move(other.children_);
}
@@ -235,6 +253,11 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
// Add all items in ChildList. Skips null Child if any.
void AddChildren(ChildList&);
+ // Add only out-of-flow items in ChildList. TODO(kojii): When |NGFragmentItem|
+ // is on, all objects should go to |NGFragmentItems| but OOF still uses
+ // fragments to propagate while in transition.
+ void AddOutOfFlowChildren(ChildList&);
+
// Creates the fragment. Can only be called once.
scoped_refptr<const NGLayoutResult> ToLineBoxFragment();
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 e3dfe0194be..c3cd893ac35 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
@@ -4,7 +4,6 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.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"
@@ -44,13 +43,6 @@ inline bool HasUnpositionedFloats(const NGInlineItemResults& item_results) {
return !item_results.IsEmpty() && item_results.back().has_unpositioned_floats;
}
-bool IsImage(const NGInlineItem& item) {
- if (!item.GetLayoutObject() || !item.GetLayoutObject()->IsLayoutImage())
- return false;
- DCHECK(item.Type() == NGInlineItem::kAtomicInline);
- return true;
-}
-
LayoutUnit ComputeInlineEndSize(const NGConstraintSpace& space,
const ComputedStyle* style) {
DCHECK(style);
@@ -131,26 +123,6 @@ scoped_refptr<const NGPhysicalTextFragment> CreateHyphenFragment(
return builder.ToTextFragment();
}
-void PreventBreakBeforeStickyImage(
- NGLineBreaker::WhitespaceState trailing_whitespace,
- const String& text,
- NGLineInfo* line_info) {
- if (trailing_whitespace != NGLineBreaker::WhitespaceState::kNone &&
- trailing_whitespace != NGLineBreaker::WhitespaceState::kUnknown)
- return;
-
- NGInlineItemResults* results = line_info->MutableResults();
- if (results->IsEmpty())
- return;
-
- // If this image follows a <wbr> the image isn't sticky.
- NGInlineItemResult* last = &results->back();
- if (text[last->start_offset] == kZeroWidthSpaceCharacter)
- return;
-
- last->can_break_after = false;
-}
-
inline void ClearNeedsLayout(const NGInlineItem& item) {
LayoutObject* layout_object = item.GetLayoutObject();
if (layout_object->NeedsLayout())
@@ -177,6 +149,7 @@ NGLineBreaker::NGLineBreaker(NGInlineNode node,
NGExclusionSpace* exclusion_space)
: line_opportunity_(line_opportunity),
node_(node),
+ mode_(mode),
is_first_formatted_line_((!break_token || (!break_token->ItemIndex() &&
!break_token->TextOffset())) &&
node.CanContainFirstFormattedLine()),
@@ -184,13 +157,15 @@ NGLineBreaker::NGLineBreaker(NGInlineNode node,
node.UseFirstLineStyle()),
in_line_height_quirks_mode_(node.InLineHeightQuirksMode()),
items_data_(node.ItemsData(use_first_line_style_)),
- mode_(mode),
+ text_content_(mode == NGLineBreakerMode::kContent
+ ? items_data_.text_content
+ : node.TextContentForContentSize(items_data_)),
constraint_space_(space),
exclusion_space_(exclusion_space),
break_token_(break_token),
- break_iterator_(items_data_.text_content),
- shaper_(items_data_.text_content),
- spacing_(items_data_.text_content),
+ break_iterator_(text_content_),
+ shaper_(text_content_),
+ spacing_(text_content_),
leading_floats_(leading_floats),
handled_leading_floats_index_(handled_leading_floats_index),
base_direction_(node_.BaseDirection()) {
@@ -198,27 +173,15 @@ NGLineBreaker::NGLineBreaker(NGInlineNode node,
break_iterator_.SetBreakSpace(BreakSpaceType::kBeforeSpaceRun);
if (break_token) {
- current_style_ = break_token->Style();
item_index_ = break_token->ItemIndex();
offset_ = break_token->TextOffset();
break_iterator_.SetStartOffset(offset_);
is_after_forced_break_ = break_token->IsForcedBreak();
items_data_.AssertOffset(item_index_, offset_);
- ignore_floats_ = break_token->IgnoreFloats();
+ // TODO(crbug.com/1013040): |break_token->Style()| should not be nullptr.
+ if (const ComputedStyle* line_initial_style = break_token->Style())
+ SetCurrentStyle(*line_initial_style);
}
-
- // There's a special intrinsic size measure quirk for images that are direct
- // children of table cells that have auto inline-size: When measuring
- // intrinsic min/max inline sizes, we pretend that it's not possible to break
- // between images, or between text and images. Note that this only applies
- // when measuring. During actual layout, on the other hand, standard breaking
- // rules are to be followed.
- // See https://quirks.spec.whatwg.org/#the-table-cell-width-calculation-quirk
- if (node.GetDocument().InQuirksMode() &&
- node.Style().Display() == EDisplay::kTableCell &&
- node.Style().LogicalWidth().IsIntrinsicOrAuto() &&
- mode != NGLineBreakerMode::kContent)
- sticky_images_quirk_ = true;
}
// Define the destructor here, so that we can forward-declare more in the
@@ -313,11 +276,13 @@ void NGLineBreaker::PrepareNextLine(NGLineInfo* line_info) {
line_info->SetTextIndent(MinimumValueForLength(length, maximum_value));
}
- // Set the initial style of this line from the break token. Example:
+ // Set the initial style of this line from the line style, if the style from
+ // the end of previous line is not available. Example:
// <p>...<span>....</span></p>
// When the line wraps in <span>, the 2nd line needs to start with the style
// of the <span>.
- SetCurrentStyle(current_style_ ? *current_style_ : line_info->LineStyle());
+ if (!current_style_)
+ SetCurrentStyle(line_info->LineStyle());
ComputeBaseDirection();
line_info->SetBaseDirection(base_direction_);
@@ -414,13 +379,6 @@ void NGLineBreaker::BreakLine(
continue;
return;
}
- if (item.Type() == NGInlineItem::kAtomicInline) {
- if (HandleAtomicInline(item, percentage_resolution_block_size_for_min_max,
- line_info)) {
- continue;
- }
- return;
- }
if (item.Type() == NGInlineItem::kCloseTag) {
HandleCloseTag(item, line_info);
continue;
@@ -446,8 +404,14 @@ void NGLineBreaker::BreakLine(
return;
}
+ if (item.Type() == NGInlineItem::kAtomicInline) {
+ HandleAtomicInline(item, percentage_resolution_block_size_for_min_max,
+ line_info);
+ continue;
+ }
if (item.Type() == NGInlineItem::kOutOfFlowPositioned) {
- AddItem(item, line_info);
+ NGInlineItemResult* item_result = AddItem(item, line_info);
+ ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_);
MoveToNextOf(item);
} else if (item.Length()) {
NOTREACHED();
@@ -473,7 +437,10 @@ void NGLineBreaker::ComputeLineLocation(NGLineInfo* line_info) const {
// Negative margins can make the position negative, but the inline size is
// always positive or 0.
LayoutUnit available_width = AvailableWidth();
- DCHECK_EQ(position_, line_info->ComputeWidth());
+
+ // Text measurement is done using floats which may introduce small rounding
+ // errors for near-saturated values.
+ DCHECK_EQ(position_.Round(), line_info->ComputeWidth().Round());
line_info->SetWidth(available_width, position_);
line_info->SetBfcOffset(
@@ -482,6 +449,49 @@ void NGLineBreaker::ComputeLineLocation(NGLineInfo* line_info) const {
line_info->UpdateTextAlign();
}
+// For Web-compatibility, allow break between an atomic inline and any adjacent
+// U+00A0 NO-BREAK SPACE character.
+// https://www.w3.org/TR/css-text-3/#line-break-details
+bool NGLineBreaker::IsAtomicInlineBeforeNoBreakSpace(
+ const NGInlineItemResult& item_result) const {
+ DCHECK(auto_wrap_);
+ DCHECK_EQ(item_result.item->Type(), NGInlineItem::kAtomicInline);
+ const String& text = Text();
+ DCHECK_GE(text.length(), item_result.end_offset);
+ return text.length() > item_result.end_offset &&
+ text[item_result.end_offset] == kNoBreakSpaceCharacter &&
+ // Except when sticky images quirk was applied.
+ text[item_result.start_offset] != kNoBreakSpaceCharacter;
+}
+
+bool NGLineBreaker::IsAtomicInlineAfterNoBreakSpace(
+ const NGInlineItemResult& item_result) const {
+ DCHECK(auto_wrap_);
+ DCHECK_EQ(item_result.item->Type(), NGInlineItem::kText);
+ const String& text = Text();
+ DCHECK_GE(text.length(), item_result.end_offset);
+ if (text[item_result.end_offset - 1] != kNoBreakSpaceCharacter ||
+ text.length() <= item_result.end_offset ||
+ text[item_result.end_offset] != kObjectReplacementCharacter)
+ return false;
+ // This kObjectReplacementCharacter can be any objects, such as a floating or
+ // an OOF object. Check if it's really an atomic inline.
+ const Vector<NGInlineItem>& items = Items();
+ for (const NGInlineItem* item = std::next(item_result.item);
+ item != items.end(); ++item) {
+ DCHECK_EQ(item->StartOffset(), item_result.end_offset);
+ if (item->Type() == NGInlineItem::kAtomicInline) {
+ // Except when sticky images quirk was applied.
+ if (UNLIKELY(text[item->StartOffset()] == kNoBreakSpaceCharacter))
+ return false;
+ return true;
+ }
+ if (item->EndOffset() > item_result.end_offset)
+ break;
+ }
+ return false;
+}
+
void NGLineBreaker::HandleText(const NGInlineItem& item,
const ShapeResult& shape_result,
NGLineInfo* line_info) {
@@ -542,7 +552,6 @@ void NGLineBreaker::HandleText(const NGInlineItem& item,
!override_break_anywhere_));
position_ += item_result->inline_size;
DCHECK_EQ(break_result == kSuccess, position_ <= available_width);
- item_result->may_break_inside = break_result == kSuccess;
MoveToNextOf(*item_result);
if (break_result == kSuccess ||
@@ -640,12 +649,12 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText(
unsigned try_count = 0;
#endif
LayoutUnit inline_size;
+ ShapingLineBreaker::Result result;
while (true) {
#if DCHECK_IS_ON()
++try_count;
DCHECK_LE(try_count, 2u);
#endif
- ShapingLineBreaker::Result result;
scoped_refptr<const ShapeResultView> shape_result = breaker.ShapeLine(
item_result->start_offset, available_width.ClampNegativeToZero(),
options, &result);
@@ -710,8 +719,18 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText(
DCHECK_EQ(item_result->end_offset, item.EndOffset());
item_result->can_break_after =
break_iterator_.IsBreakable(item_result->end_offset);
+ if (!item_result->can_break_after && item.Type() == NGInlineItem::kText &&
+ IsAtomicInlineAfterNoBreakSpace(*item_result))
+ item_result->can_break_after = true;
trailing_whitespace_ = WhitespaceState::kUnknown;
}
+
+ // This result is not breakable any further if overflow. This information is
+ // useful to optimize |HandleOverflow()|.
+ item_result->may_break_inside = !result.is_overflow;
+
+ // TODO(crbug.com/1003742): We should use |result.is_overflow| here. For now,
+ // use |inline_size| because some tests rely on this behavior.
return inline_size <= available_width ? kSuccess : kOverflow;
}
@@ -1179,30 +1198,13 @@ void NGLineBreaker::HandleBidiControlItem(const NGInlineItem& item,
MoveToNextOf(item);
}
-bool NGLineBreaker::HandleAtomicInline(
+void NGLineBreaker::HandleAtomicInline(
const NGInlineItem& item,
LayoutUnit percentage_resolution_block_size_for_min_max,
NGLineInfo* line_info) {
DCHECK_EQ(item.Type(), NGInlineItem::kAtomicInline);
DCHECK(item.Style());
const ComputedStyle& style = *item.Style();
- const NGInlineItemResults& item_results = line_info->Results();
-
- // If the sticky images quirk is enabled, and this is an image that
- // follows text that doesn't end with something breakable, we cannot break
- // between the two items.
- bool is_sticky_image = sticky_images_quirk_ && IsImage(item);
- if (UNLIKELY(is_sticky_image)) {
- PreventBreakBeforeStickyImage(trailing_whitespace_, Text(), line_info);
- }
-
- // Atomic inline is handled as if it is trailable, because it can prevent
- // break-before. Check if the line should break before this item, after the
- // last item's |can_break_after| is finalized for the quirk above.
- if (state_ == LineBreakState::kTrailing && CanBreakAfterLast(item_results)) {
- line_info->SetIsLastLine(false);
- return false;
- }
NGInlineItemResult* item_result = AddItem(item, line_info);
item_result->should_create_line_box = true;
@@ -1251,21 +1253,11 @@ bool NGLineBreaker::HandleAtomicInline(
trailing_whitespace_ = WhitespaceState::kNone;
position_ += item_result->inline_size;
ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_);
+ if (!item_result->can_break_after && auto_wrap_ &&
+ IsAtomicInlineBeforeNoBreakSpace(*item_result))
+ item_result->can_break_after = true;
- if (UNLIKELY(is_sticky_image)) {
- const auto& items = Items();
- if (item_index_ + 1 < items.size()) {
- DCHECK_EQ(&item, &items[item_index_]);
- const auto& next_item = items[item_index_ + 1];
- // This is an image, and we don't want to break after it, unless what
- // comes after provides a break opportunity. Look ahead. We only want to
- // break if the next item is an atomic inline that's not an image.
- if (next_item.Type() != NGInlineItem::kAtomicInline || IsImage(next_item))
- item_result->can_break_after = false;
- }
- }
MoveToNextOf(item);
- return true;
}
// Performs layout and positions a float.
@@ -1306,9 +1298,6 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
return;
}
- if (ignore_floats_)
- return;
-
// Make sure we populate the positioned_float inside the |item_result|.
if (item_index_ <= handled_leading_floats_index_ &&
!leading_floats_.IsEmpty()) {
@@ -1372,7 +1361,7 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
NGLayoutOpportunity opportunity = exclusion_space_->FindLayoutOpportunity(
{constraint_space_.BfcOffset().line_offset, bfc_block_offset},
- constraint_space_.AvailableSize().inline_size, LogicalSize());
+ constraint_space_.AvailableSize().inline_size);
DCHECK_EQ(bfc_block_offset, opportunity.rect.BlockStartOffset());
@@ -1759,6 +1748,23 @@ const ComputedStyle& NGLineBreaker::ComputeCurrentStyle(
}
void NGLineBreaker::SetCurrentStyle(const ComputedStyle& style) {
+ if (&style == current_style_.get()) {
+#if DCHECK_IS_ON()
+ // Check that cache fields are already setup correctly.
+ DCHECK_EQ(auto_wrap_, style.AutoWrap());
+ if (auto_wrap_) {
+ DCHECK_EQ(enable_soft_hyphen_, style.GetHyphens() != Hyphens::kNone);
+ DCHECK_EQ(break_iterator_.Locale(), style.LocaleForLineBreakIterator());
+ }
+ ShapeResultSpacing<String> spacing(spacing_.Text());
+ spacing.SetSpacing(style.GetFontDescription());
+ DCHECK_EQ(spacing.LetterSpacing(), spacing_.LetterSpacing());
+ DCHECK_EQ(spacing.WordSpacing(), spacing_.WordSpacing());
+#endif
+ return;
+ }
+ current_style_ = &style;
+
auto_wrap_ = style.AutoWrap();
if (auto_wrap_) {
@@ -1794,17 +1800,10 @@ void NGLineBreaker::SetCurrentStyle(const ComputedStyle& style) {
if (style.WhiteSpace() == EWhiteSpace::kBreakSpaces)
break_iterator_.SetBreakSpace(BreakSpaceType::kAfterEverySpace);
- }
-
- // The above calls are cheap & necessary. But the following are expensive
- // and do not need to be reset every time if the style doesn't change,
- // so avoid them if possible.
- if (&style == current_style_.get())
- return;
- current_style_ = &style;
- if (auto_wrap_)
break_iterator_.SetLocale(style.LocaleForLineBreakIterator());
+ }
+
spacing_.SetSpacing(style.GetFontDescription());
}
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 a90313b2f08..fc24745e176 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
@@ -93,9 +93,11 @@ class CORE_EXPORT NGLineBreaker {
}
private:
- const String& Text() const { return items_data_.text_content; }
+ const String& Text() const { return text_content_; }
const Vector<NGInlineItem>& Items() const { return items_data_.items; }
+ String TextContentForLineBreak() const;
+
NGInlineItemResult* AddItem(const NGInlineItem&,
unsigned end_offset,
NGLineInfo*);
@@ -159,10 +161,14 @@ class CORE_EXPORT NGLineBreaker {
void HandleControlItem(const NGInlineItem&, NGLineInfo*);
void HandleBidiControlItem(const NGInlineItem&, NGLineInfo*);
- bool HandleAtomicInline(
+ void HandleAtomicInline(
const NGInlineItem&,
LayoutUnit percentage_resolution_block_size_for_min_max,
NGLineInfo*);
+ bool IsAtomicInlineAfterNoBreakSpace(
+ const NGInlineItemResult& item_result) const;
+ bool IsAtomicInlineBeforeNoBreakSpace(
+ const NGInlineItemResult& item_result) const;
void HandleFloat(const NGInlineItem&,
Vector<LayoutObject*>* out_floats_for_min_max,
NGLineInfo*);
@@ -208,6 +214,8 @@ class CORE_EXPORT NGLineBreaker {
NGInlineNode node_;
+ NGLineBreakerMode mode_;
+
// True if this line is the "first formatted line".
// https://www.w3.org/TR/CSS22/selector.html#first-formatted-line
bool is_first_formatted_line_ = false;
@@ -240,15 +248,13 @@ class CORE_EXPORT NGLineBreaker {
// the next line.
bool is_after_forced_break_ = false;
- bool ignore_floats_ = false;
-
- // Set in quirks mode when we're not supposed to break inside table cells
- // between images, and between text and images.
- bool sticky_images_quirk_ = false;
-
const NGInlineItemsData& items_data_;
- NGLineBreakerMode mode_;
+ // The text content of this node. This is same as |items_data_.text_content|
+ // except when sticky images quirk is needed. See
+ // |NGInlineNode::TextContentForContentSize|.
+ String text_content_;
+
const NGConstraintSpace& constraint_space_;
NGExclusionSpace* exclusion_space_;
scoped_refptr<const NGInlineBreakToken> break_token_;
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 df93ec3768d..34eba2935cd 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
@@ -34,12 +34,11 @@ class NGLineBreakerTest : public NGLayoutTest {
node.PrepareLayoutIfNeeded();
- NGConstraintSpace space =
- NGConstraintSpaceBuilder(WritingMode::kHorizontalTb,
- WritingMode::kHorizontalTb,
- /* is_new_fc */ false)
- .SetAvailableSize({available_width, kIndefiniteSize})
- .ToConstraintSpace();
+ NGConstraintSpaceBuilder builder(WritingMode::kHorizontalTb,
+ WritingMode::kHorizontalTb,
+ /* is_new_fc */ false);
+ builder.SetAvailableSize({available_width, kIndefiniteSize});
+ NGConstraintSpace space = builder.ToConstraintSpace();
scoped_refptr<NGInlineBreakToken> break_token;
@@ -307,6 +306,26 @@ TEST_F(NGLineBreakerTest, WrapLastWord) {
EXPECT_EQ("AAA BB CC", ToString(lines[1], node));
}
+TEST_F(NGLineBreakerTest, WrapLetterSpacing) {
+ NGInlineNode node = CreateInlineNode(R"HTML(
+ <!DOCTYPE html>
+ <style>
+ #container {
+ font: 10px/1 Times;
+ letter-spacing: 10px;
+ width: 0px;
+ }
+ </style>
+ <div id=container>Star Wars</div>
+ )HTML");
+
+ Vector<NGInlineItemResults> lines;
+ lines = BreakLines(node, LayoutUnit(100));
+ EXPECT_EQ(2u, lines.size());
+ EXPECT_EQ("Star", ToString(lines[0], node));
+ EXPECT_EQ("Wars", ToString(lines[1], node));
+}
+
TEST_F(NGLineBreakerTest, BoundaryInWord) {
LoadAhem();
NGInlineNode node = CreateInlineNode(R"HTML(
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 836de6ae3db..ddadc0953aa 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
@@ -97,7 +97,9 @@ LayoutUnit NGLineTruncator::TruncateLine(
// Create the ellipsis, associating it with the ellipsized child.
LayoutObject* ellipsized_layout_object =
ellpisized_child->PhysicalFragment()->GetMutableLayoutObject();
- DCHECK(ellipsized_layout_object && ellipsized_layout_object->IsInline());
+ DCHECK(ellipsized_layout_object && ellipsized_layout_object->IsInline() &&
+ (ellipsized_layout_object->IsText() ||
+ ellipsized_layout_object->IsAtomicInlineLevel()));
NGTextFragmentBuilder builder(line_style_->GetWritingMode());
builder.SetText(ellipsized_layout_object, ellipsis_text, ellipsis_style,
true /* is_ellipsis_style */,
@@ -171,6 +173,12 @@ bool NGLineTruncator::EllipsizeChild(
if (!child->HasInFlowFragment())
return false;
+ // Inline boxes should not be ellipsized. Usually they will be created in the
+ // later phase, but empty inline box are already created.
+ if (child->layout_result &&
+ child->layout_result->PhysicalFragment().IsInlineBox())
+ return false;
+
// Can't place ellipsis if this child is completely outside of the box.
LayoutUnit child_inline_offset =
IsLtr(line_direction_)
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 7a453e31e5d..7fc614ba21c 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
@@ -57,9 +57,9 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final
NGLineHeightMetrics BaselineMetrics(FontBaseline) const;
// Scrollable overflow. including contents, in the local coordinate.
- // ScrollableOverflow is not precomputed/cached because it cannot be computed
- // when LineBox is generated because it needs container dimensions to
- // resolve relative position of its children.
+ // |ScrollableOverflow| is not precomputed/cached because it cannot be
+ // computed when LineBox is generated because it needs container dimensions
+ // to resolve relative position of its children.
PhysicalRect ScrollableOverflow(const LayoutObject* container,
const ComputedStyle* container_style,
PhysicalSize container_physical_size) const;
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 b4dc039579c..10cae6bffed 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
@@ -11,6 +11,7 @@
#include "third_party/blink/renderer/core/layout/line/line_orientation_utils.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/layout/ng/ng_ink_overflow.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
@@ -20,9 +21,8 @@ namespace blink {
namespace {
struct SameSizeAsNGPhysicalTextFragment : NGPhysicalFragment {
- void* pointers[2];
+ void* pointers[3];
unsigned offsets[2];
- PhysicalRect rect;
};
static_assert(sizeof(NGPhysicalTextFragment) ==
@@ -84,23 +84,6 @@ NGPhysicalTextFragment::NGPhysicalTextFragment(NGTextFragmentBuilder* builder)
ink_overflow_computed_ = false;
}
-// Convert logical cooridnate to local physical coordinate.
-PhysicalRect NGPhysicalTextFragment::ConvertToLocal(
- const LayoutRect& logical_rect) const {
- switch (LineOrientation()) {
- case NGLineOrientation::kHorizontal:
- return PhysicalRect(logical_rect);
- case NGLineOrientation::kClockWiseVertical:
- return {size_.width - logical_rect.MaxY(), logical_rect.X(),
- logical_rect.Height(), logical_rect.Width()};
- case NGLineOrientation::kCounterClockWiseVertical:
- return {logical_rect.Y(), size_.height - logical_rect.MaxX(),
- logical_rect.Height(), logical_rect.Width()};
- }
- NOTREACHED();
- return PhysicalRect(logical_rect);
-}
-
// Compute the inline position from text offset, in logical coordinate relative
// to this fragment.
LayoutUnit NGPhysicalTextFragment::InlinePositionForOffset(
@@ -175,73 +158,20 @@ PhysicalRect NGPhysicalTextFragment::LocalRect(unsigned start_offset,
PhysicalRect NGPhysicalTextFragment::SelfInkOverflow() const {
if (!ink_overflow_computed_)
ComputeSelfInkOverflow();
- return self_ink_overflow_;
-}
-
-void NGPhysicalTextFragment::ClearSelfInkOverflow() const {
- self_ink_overflow_ = LocalRect();
+ if (ink_overflow_)
+ return ink_overflow_->self_ink_overflow;
+ return LocalRect();
}
void NGPhysicalTextFragment::ComputeSelfInkOverflow() const {
ink_overflow_computed_ = true;
if (UNLIKELY(!shape_result_)) {
- ClearSelfInkOverflow();
+ ink_overflow_ = nullptr;
return;
}
- // Glyph bounds is in logical coordinate, origin at the alphabetic baseline.
- FloatRect text_ink_bounds = Style().GetFont().TextInkBounds(PaintInfo());
- LayoutRect ink_overflow = EnclosingLayoutRect(text_ink_bounds);
-
- // Make the origin at the logical top of this fragment.
- const ComputedStyle& style = Style();
- const Font& font = style.GetFont();
- if (const SimpleFontData* font_data = font.PrimaryFont()) {
- ink_overflow.SetY(
- ink_overflow.Y() +
- font_data->GetFontMetrics().FixedAscent(kAlphabeticBaseline));
- }
-
- if (float stroke_width = style.TextStrokeWidth()) {
- ink_overflow.Inflate(LayoutUnit::FromFloatCeil(stroke_width / 2.0f));
- }
-
- if (style.GetTextEmphasisMark() != TextEmphasisMark::kNone) {
- LayoutUnit emphasis_mark_height =
- LayoutUnit(font.EmphasisMarkHeight(style.TextEmphasisMarkString()));
- DCHECK_GT(emphasis_mark_height, LayoutUnit());
- if (style.GetTextEmphasisLineLogicalSide() == LineLogicalSide::kOver) {
- ink_overflow.ShiftYEdgeTo(
- std::min(ink_overflow.Y(), -emphasis_mark_height));
- } else {
- LayoutUnit logical_height =
- style.IsHorizontalWritingMode() ? Size().height : Size().width;
- ink_overflow.ShiftMaxYEdgeTo(
- std::max(ink_overflow.MaxY(), logical_height + emphasis_mark_height));
- }
- }
-
- if (ShadowList* text_shadow = style.TextShadow()) {
- LayoutRectOutsets text_shadow_logical_outsets =
- LineOrientationLayoutRectOutsets(
- LayoutRectOutsets(text_shadow->RectOutsetsIncludingOriginal()),
- style.GetWritingMode());
- text_shadow_logical_outsets.ClampNegativeToZero();
- ink_overflow.Expand(text_shadow_logical_outsets);
- }
-
- // Uniting the frame rect ensures that non-ink spaces such side bearings, or
- // even space characters, are included in the visual rect for decorations.
- PhysicalRect local_ink_overflow = ConvertToLocal(ink_overflow);
- PhysicalRect local_rect = LocalRect();
- if (local_rect.Contains(local_ink_overflow)) {
- self_ink_overflow_ = local_rect;
- return;
- }
- local_ink_overflow.Unite(local_rect);
- local_ink_overflow.ExpandEdgesToPixelBoundaries();
- self_ink_overflow_ = local_ink_overflow;
+ ink_overflow_ = NGInkOverflow::TextInkOverflow(PaintInfo(), Style(), Size());
}
scoped_refptr<const NGPhysicalTextFragment>
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 a0fc73c0160..e4b7ba1ca08 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
@@ -7,6 +7,7 @@
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_end_effect.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_ink_overflow.h"
#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"
@@ -17,9 +18,9 @@
namespace blink {
-struct PhysicalRect;
class NGTextFragmentBuilder;
class NGPhysicalTextFragment;
+struct PhysicalRect;
enum class AdjustMidCluster;
@@ -149,10 +150,7 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
LayoutUnit (*round)(float),
AdjustMidCluster) const;
- PhysicalRect ConvertToLocal(const LayoutRect&) const;
-
void ComputeSelfInkOverflow() const;
- void ClearSelfInkOverflow() const;
// 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.
@@ -166,7 +164,7 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
// Fragments are immutable but allow certain expensive data, specifically ink
// overflow, to be cached as long as it is guaranteed to always recompute to
// the same value.
- mutable PhysicalRect self_ink_overflow_;
+ mutable std::unique_ptr<NGInkOverflow> ink_overflow_;
friend class NGTextFragmentBuilder;
};