summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/layout/ng/inline
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-08-24 12:15:48 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-08-28 13:30:04 +0000
commitb014812705fc80bff0a5c120dfcef88f349816dc (patch)
tree25a2e2d9fa285f1add86aa333389a839f81a39ae /chromium/third_party/blink/renderer/core/layout/ng/inline
parent9f4560b1027ae06fdb497023cdcaf91b8511fa74 (diff)
downloadqtwebengine-chromium-b014812705fc80bff0a5c120dfcef88f349816dc.tar.gz
BASELINE: Update Chromium to 68.0.3440.125
Change-Id: I23f19369e01f688e496f5bf179abb521ad73874f 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.h64
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc313
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h55
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position_test.cc (renamed from chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect_test.cc)88
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc276
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.h45
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc102
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h8
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc3
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc10
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc58
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h68
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc10
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h12
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc141
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h16
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc108
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc182
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h10
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc4
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc294
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h50
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.cc7
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h32
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc29
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc34
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h21
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc306
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h48
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc15
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.cc3
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h1
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc201
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h51
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_utils.cc31
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_utils.h25
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc24
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h14
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.h4
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc5
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc47
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h17
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc17
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc97
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h17
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc8
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h1
47 files changed, 2013 insertions, 959 deletions
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h
new file mode 100644
index 00000000000..8ea80821037
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h
@@ -0,0 +1,64 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_LAYOUT_NG_TEXT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_LAYOUT_NG_TEXT_H_
+
+#include "third_party/blink/renderer/core/layout/layout_text.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h"
+
+namespace blink {
+
+class NGInlineItem;
+
+// This overrides the default LayoutText to reference LayoutNGInlineItems
+// instead of InlineTextBoxes.
+//
+// ***** INLINE ITEMS OWNERSHIP *****
+// NGInlineItems in items_ are not owned by LayoutText but are pointers into the
+// LayoutNGBlockFlow's items_. Should not be accessed outside of layout.
+class CORE_EXPORT LayoutNGText : public LayoutText {
+ public:
+ LayoutNGText(Node* node, scoped_refptr<StringImpl> text)
+ : LayoutText(node, text) {}
+
+ bool IsOfType(LayoutObjectType type) const override {
+ return type == kLayoutObjectNGText || LayoutText::IsOfType(type);
+ }
+
+ bool HasValidLayout() const { return valid_ng_items_; }
+ const Vector<NGInlineItem*>& InlineItems() const {
+ DCHECK(valid_ng_items_);
+ return inline_items_;
+ }
+
+ // Inline items depends on context. It needs to be invalidated not only when
+ // it was inserted/changed but also it was moved.
+ void InvalidateInlineItems() { valid_ng_items_ = false; }
+
+ void ClearInlineItems() {
+ inline_items_.clear();
+ valid_ng_items_ = false;
+ }
+
+ void AddInlineItem(NGInlineItem* item) {
+ inline_items_.push_back(item);
+ valid_ng_items_ = true;
+ }
+
+ protected:
+ void InsertedIntoTree() override {
+ valid_ng_items_ = false;
+ LayoutText::InsertedIntoTree();
+ }
+
+ private:
+ Vector<NGInlineItem*> inline_items_;
+};
+
+DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGText, IsLayoutNGText());
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_LAYOUT_NG_TEXT_H_
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc
new file mode 100644
index 00000000000..cadd9c38992
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc
@@ -0,0 +1,313 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h"
+
+#include "third_party/blink/renderer/core/editing/inline_box_traversal.h"
+#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
+#include "third_party/blink/renderer/core/editing/text_affinity.h"
+#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
+#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
+#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment_traversal.h"
+
+namespace blink {
+
+namespace {
+
+void AssertValidPositionForCaretPositionComputation(
+ const PositionWithAffinity& position) {
+#if DCHECK_IS_ON()
+ DCHECK(NGOffsetMapping::AcceptsPosition(position.GetPosition()));
+ const LayoutObject* layout_object = position.AnchorNode()->GetLayoutObject();
+ DCHECK(layout_object);
+ DCHECK(layout_object->IsText() || layout_object->IsAtomicInlineLevel());
+#endif
+}
+
+// The calculation takes the following input:
+// - An inline formatting context as a |LayoutBlockFlow|
+// - An offset in the |text_content_| string of the above context
+// - A TextAffinity
+//
+// The calculation iterates all inline fragments in the context, and tries to
+// compute an NGCaretPosition using the "caret resolution process" below:
+//
+// The (offset, affinity) pair is compared against each inline fragment to see
+// if the corresponding caret should be placed in the fragment, using the
+// |TryResolveCaretPositionInXXX()| functions. These functions may return:
+// - Failed, indicating that the caret must not be placed in the fragment;
+// - Resolved, indicating that the care should be placed in the fragment, and
+// no further search is required. The result NGCaretPosition is returned
+// together.
+// - FoundCandidate, indicating that the caret may be placed in the fragment;
+// however, further search may find a better position. The candidate
+// NGCaretPosition is also returned together.
+
+enum class ResolutionType { kFailed, kFoundCandidate, kResolved };
+struct CaretPositionResolution {
+ ResolutionType type = ResolutionType::kFailed;
+ NGCaretPosition caret_position;
+};
+
+bool CanResolveCaretPositionBeforeFragment(const NGPaintFragment& fragment,
+ TextAffinity affinity) {
+ if (affinity == TextAffinity::kDownstream)
+ return true;
+ const NGPaintFragment* current_line_paint = fragment.ContainerLineBox();
+ const NGPhysicalLineBoxFragment& current_line =
+ ToNGPhysicalLineBoxFragment(current_line_paint->PhysicalFragment());
+ // A fragment after line wrap must be the first logical leaf in its line.
+ if (&fragment.PhysicalFragment() != current_line.FirstLogicalLeaf())
+ return true;
+ const NGPaintFragment* last_line_paint =
+ NGPaintFragmentTraversal::PreviousLineOf(*current_line_paint);
+ return !last_line_paint ||
+ !ToNGPhysicalLineBoxFragment(last_line_paint->PhysicalFragment())
+ .HasSoftWrapToNextLine();
+}
+
+bool CanResolveCaretPositionAfterFragment(const NGPaintFragment& fragment,
+ TextAffinity affinity) {
+ if (affinity == TextAffinity::kUpstream)
+ return true;
+ const NGPaintFragment* current_line_paint = fragment.ContainerLineBox();
+ const NGPhysicalLineBoxFragment& current_line =
+ ToNGPhysicalLineBoxFragment(current_line_paint->PhysicalFragment());
+ // A fragment before line wrap must be the last logical leaf in its line.
+ if (&fragment.PhysicalFragment() != current_line.LastLogicalLeaf())
+ return true;
+ return !current_line.HasSoftWrapToNextLine();
+}
+
+// Returns a |kFailed| resolution if |offset| doesn't belong to the text
+// fragment. Otherwise, return either |kFoundCandidate| or |kResolved| depending
+// on |affinity|.
+CaretPositionResolution TryResolveCaretPositionInTextFragment(
+ const NGPaintFragment& paint_fragment,
+ unsigned offset,
+ TextAffinity affinity) {
+ DCHECK(paint_fragment.PhysicalFragment().IsText());
+ const NGPhysicalTextFragment& fragment =
+ ToNGPhysicalTextFragment(paint_fragment.PhysicalFragment());
+ if (fragment.IsAnonymousText())
+ return CaretPositionResolution();
+
+ const NGOffsetMapping& mapping =
+ *NGOffsetMapping::GetFor(paint_fragment.GetLayoutObject());
+
+ // A text fragment natually allows caret placement in offset range
+ // [StartOffset(), EndOffset()], i.e., from before the first character to
+ // after the last character.
+ // Besides, leading/trailing bidi control characters are ignored since their
+ // two sides are considered the same caret position. Hence, if there are n and
+ // m leading and trailing bidi control characters, then the allowed offset
+ // range is [StartOffset() - n, EndOffset() + m].
+ // Note that we don't ignore other characters that are not in fragments. For
+ // example, a trailing space of a line is not in any fragment, but its two
+ // sides are still different caret positions, so we don't ignore it.
+ if (offset < fragment.StartOffset() &&
+ !mapping.HasBidiControlCharactersOnly(offset, fragment.StartOffset()))
+ return CaretPositionResolution();
+ if (offset > fragment.EndOffset() &&
+ !mapping.HasBidiControlCharactersOnly(fragment.EndOffset(), offset))
+ return CaretPositionResolution();
+
+ offset = std::max(offset, fragment.StartOffset());
+ offset = std::min(offset, fragment.EndOffset());
+ NGCaretPosition candidate = {&paint_fragment,
+ NGCaretPositionType::kAtTextOffset, offset};
+
+ // Offsets in the interior of a fragment can be resolved directly.
+ if (offset > fragment.StartOffset() && offset < fragment.EndOffset())
+ return {ResolutionType::kResolved, candidate};
+
+ if (offset == fragment.StartOffset() &&
+ CanResolveCaretPositionBeforeFragment(paint_fragment, affinity)) {
+ return {ResolutionType::kResolved, candidate};
+ }
+
+ if (offset == fragment.EndOffset() && !fragment.IsLineBreak() &&
+ CanResolveCaretPositionAfterFragment(paint_fragment, affinity)) {
+ return {ResolutionType::kResolved, candidate};
+ }
+
+ // We may have a better candidate
+ return {ResolutionType::kFoundCandidate, candidate};
+}
+
+unsigned GetTextOffsetBefore(const NGPhysicalFragment& fragment) {
+ // TODO(xiaochengh): Design more straightforward way to get text offset of
+ // atomic inline box.
+ DCHECK(fragment.IsAtomicInline());
+ const Node* node = fragment.GetNode();
+ DCHECK(node);
+ const Position before_node = Position::BeforeNode(*node);
+ base::Optional<unsigned> maybe_offset_before =
+ NGOffsetMapping::GetFor(before_node)->GetTextContentOffset(before_node);
+ // We should have offset mapping for atomic inline boxes.
+ DCHECK(maybe_offset_before.has_value());
+ return maybe_offset_before.value();
+}
+
+// Returns a |kFailed| resolution if |offset| doesn't belong to the atomic
+// inline box fragment. Otherwise, return either |kFoundCandidate| or
+// |kResolved| depending on |affinity|.
+CaretPositionResolution TryResolveCaretPositionByBoxFragmentSide(
+ const NGPaintFragment& fragment,
+ unsigned offset,
+ TextAffinity affinity) {
+ if (!fragment.GetNode()) {
+ // TODO(xiaochengh): This leads to false negatives for, e.g., RUBY, where an
+ // anonymous wrapping inline block is created.
+ return CaretPositionResolution();
+ }
+
+ const unsigned offset_before =
+ GetTextOffsetBefore(fragment.PhysicalFragment());
+ const unsigned offset_after = offset_before + 1;
+ // TODO(xiaochengh): Ignore bidi control characters before & after the box.
+ if (offset != offset_before && offset != offset_after)
+ return CaretPositionResolution();
+ const NGCaretPositionType position_type =
+ offset == offset_before ? NGCaretPositionType::kBeforeBox
+ : NGCaretPositionType::kAfterBox;
+ NGCaretPosition candidate{&fragment, position_type, base::nullopt};
+
+ if (offset == offset_before &&
+ CanResolveCaretPositionBeforeFragment(fragment, affinity)) {
+ return {ResolutionType::kResolved, candidate};
+ }
+
+ if (offset == offset_after &&
+ CanResolveCaretPositionAfterFragment(fragment, affinity)) {
+ return {ResolutionType::kResolved, candidate};
+ }
+
+ return {ResolutionType::kFoundCandidate, candidate};
+}
+
+CaretPositionResolution TryResolveCaretPositionWithFragment(
+ const NGPaintFragment& paint_fragment,
+ unsigned offset,
+ TextAffinity affinity) {
+ const NGPhysicalFragment& fragment = paint_fragment.PhysicalFragment();
+ if (fragment.IsText()) {
+ return TryResolveCaretPositionInTextFragment(paint_fragment, offset,
+ affinity);
+ }
+ if (fragment.IsBox() && fragment.IsAtomicInline()) {
+ return TryResolveCaretPositionByBoxFragmentSide(paint_fragment, offset,
+ affinity);
+ }
+ return CaretPositionResolution();
+}
+
+bool NeedsBidiAdjustment(const NGCaretPosition& caret_position) {
+ if (caret_position.IsNull())
+ return false;
+ if (caret_position.position_type != NGCaretPositionType::kAtTextOffset)
+ return true;
+ DCHECK(caret_position.text_offset.has_value());
+ DCHECK(caret_position.fragment->PhysicalFragment().IsText());
+ const NGPhysicalTextFragment& text_fragment =
+ ToNGPhysicalTextFragment(caret_position.fragment->PhysicalFragment());
+ DCHECK_GE(*caret_position.text_offset, text_fragment.StartOffset());
+ DCHECK_LE(*caret_position.text_offset, text_fragment.EndOffset());
+ // Bidi adjustment is needed only for caret positions at bidi boundaries.
+ // Caret positions in the middle of a text fragment can't be at bidi
+ // boundaries, and hence, don't need any adjustment.
+ return *caret_position.text_offset == text_fragment.StartOffset() ||
+ *caret_position.text_offset == text_fragment.EndOffset();
+}
+
+NGCaretPosition AdjustCaretPositionForBidiText(
+ const NGCaretPosition& caret_position) {
+ if (!NeedsBidiAdjustment(caret_position))
+ return caret_position;
+ return BidiAdjustment::AdjustForCaretPositionResolution(caret_position);
+}
+
+} // namespace
+
+// The main function for compute an NGCaretPosition. See the comments at the top
+// of this file for details.
+NGCaretPosition ComputeNGCaretPosition(const LayoutBlockFlow& context,
+ unsigned offset,
+ TextAffinity affinity) {
+ const NGPaintFragment* root_fragment = context.PaintFragment();
+ DCHECK(root_fragment) << "no paint fragment on layout object " << &context;
+
+ NGCaretPosition candidate;
+ for (const auto& child :
+ NGPaintFragmentTraversal::InlineDescendantsOf(*root_fragment)) {
+ const CaretPositionResolution resolution =
+ TryResolveCaretPositionWithFragment(*child.fragment, offset, affinity);
+
+ if (resolution.type == ResolutionType::kFailed)
+ continue;
+
+ // TODO(xiaochengh): Handle caret poisition in empty container (e.g. empty
+ // line box).
+
+ if (resolution.type == ResolutionType::kResolved)
+ return AdjustCaretPositionForBidiText(resolution.caret_position);
+
+ DCHECK_EQ(ResolutionType::kFoundCandidate, resolution.type);
+ // TODO(xiaochengh): We are not sure if we can ever find multiple
+ // candidates. Handle it once reached.
+ DCHECK(candidate.IsNull());
+ candidate = resolution.caret_position;
+ }
+
+ return AdjustCaretPositionForBidiText(candidate);
+}
+
+NGCaretPosition ComputeNGCaretPosition(const PositionWithAffinity& position) {
+ AssertValidPositionForCaretPositionComputation(position);
+ const LayoutBlockFlow* context =
+ NGInlineFormattingContextOf(position.GetPosition());
+ if (!context)
+ return NGCaretPosition();
+
+ const NGOffsetMapping* mapping = NGOffsetMapping::GetFor(context);
+ DCHECK(mapping);
+ const base::Optional<unsigned> maybe_offset =
+ mapping->GetTextContentOffset(position.GetPosition());
+ if (!maybe_offset.has_value()) {
+ // TODO(xiaochengh): Investigate if we reach here.
+ NOTREACHED();
+ return NGCaretPosition();
+ }
+
+ const unsigned offset = maybe_offset.value();
+ const TextAffinity affinity = position.Affinity();
+ return ComputeNGCaretPosition(*context, offset, affinity);
+}
+
+Position NGCaretPosition::ToPositionInDOMTree() const {
+ if (!fragment)
+ return Position();
+ switch (position_type) {
+ case NGCaretPositionType::kBeforeBox:
+ if (!fragment->GetNode())
+ return Position();
+ return Position::BeforeNode(*fragment->GetNode());
+ case NGCaretPositionType::kAfterBox:
+ if (!fragment->GetNode())
+ return Position();
+ return Position::AfterNode(*fragment->GetNode());
+ case NGCaretPositionType::kAtTextOffset:
+ DCHECK(text_offset.has_value());
+ const NGOffsetMapping* mapping =
+ NGOffsetMapping::GetFor(fragment->GetLayoutObject());
+ return mapping->GetFirstPosition(*text_offset);
+ }
+ NOTREACHED();
+ return Position();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h
new file mode 100644
index 00000000000..e6a963099c6
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h
@@ -0,0 +1,55 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_CARET_POSITION_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_CARET_POSITION_H_
+
+#include "base/optional.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/editing/forward.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+
+namespace blink {
+
+class NGPaintFragment;
+class LayoutBlockFlow;
+
+// An NGCaretPosition indicates a caret position relative to an inline
+// NGPaintFragment:
+// - When |fragment| is box, |position_type| is either |kBeforeBox| or
+// |kAfterBox|, indicating either of the two caret positions by the box sides;
+// |text_offset| is |nullopt| in this case.
+// - When |fragment| is text, |position_type| is |kAtTextOffset|, and
+// |text_offset| is in the text offset range of the fragment.
+//
+// TODO(xiaochengh): Support "in empty container" caret type
+
+enum class NGCaretPositionType { kBeforeBox, kAfterBox, kAtTextOffset };
+struct NGCaretPosition {
+ DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
+
+ bool IsNull() const { return !fragment; }
+
+ Position ToPositionInDOMTree() const;
+
+ const NGPaintFragment* fragment = nullptr; // owned by root LayoutNGMixin
+ NGCaretPositionType position_type;
+ base::Optional<unsigned> text_offset;
+};
+
+// Given an inline formatting context, a text offset in the context and a text
+// affinity, returns the corresponding NGCaretPosition, or null if not found.
+// Note that in many cases, null result indicates that we have reached an
+// unexpected case that is not properly handled.
+CORE_EXPORT NGCaretPosition ComputeNGCaretPosition(const LayoutBlockFlow&,
+ unsigned,
+ TextAffinity);
+
+// Shorthand of the above when the input is a position instead of a
+// (context, offset) pair.
+NGCaretPosition ComputeNGCaretPosition(const PositionWithAffinity&);
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_CARET_POSITION_H_
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position_test.cc
index 1ed9a35639e..da232c2bf83 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position_test.cc
@@ -2,8 +2,9 @@
// 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_caret_rect.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h"
+#include "third_party/blink/renderer/core/editing/text_affinity.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
@@ -14,9 +15,9 @@
namespace blink {
-class NGCaretRectTest : public NGLayoutTest {
+class NGCaretPositionTest : public NGLayoutTest {
public:
- NGCaretRectTest() : NGLayoutTest() {}
+ NGCaretPositionTest() : NGLayoutTest() {}
void SetUp() override {
NGLayoutTest::SetUp();
@@ -69,31 +70,31 @@ class NGCaretRectTest : public NGLayoutTest {
EXPECT_EQ(caret.text_offset, offset_) << caret.text_offset.value_or(-1); \
}
-TEST_F(NGCaretRectTest, CaretPositionInOneLineOfText) {
+TEST_F(NGCaretPositionTest, CaretPositionInOneLineOfText) {
SetInlineFormattingContext("t", "foo", 3);
const Node* text = container_->firstChild();
const NGPhysicalFragment* text_fragment = FragmentOf(text);
// Beginning of line
TEST_CARET(ComputeNGCaretPosition(0, TextAffinity::kDownstream),
- text_fragment, kAtTextOffset, Optional<unsigned>(0));
+ text_fragment, kAtTextOffset, base::Optional<unsigned>(0));
TEST_CARET(ComputeNGCaretPosition(0, TextAffinity::kUpstream), text_fragment,
- kAtTextOffset, Optional<unsigned>(0));
+ kAtTextOffset, base::Optional<unsigned>(0));
// Middle in the line
TEST_CARET(ComputeNGCaretPosition(1, TextAffinity::kDownstream),
- text_fragment, kAtTextOffset, Optional<unsigned>(1));
+ text_fragment, kAtTextOffset, base::Optional<unsigned>(1));
TEST_CARET(ComputeNGCaretPosition(1, TextAffinity::kUpstream), text_fragment,
- kAtTextOffset, Optional<unsigned>(1));
+ kAtTextOffset, base::Optional<unsigned>(1));
// End of line
TEST_CARET(ComputeNGCaretPosition(3, TextAffinity::kDownstream),
- text_fragment, kAtTextOffset, Optional<unsigned>(3));
+ text_fragment, kAtTextOffset, base::Optional<unsigned>(3));
TEST_CARET(ComputeNGCaretPosition(3, TextAffinity::kUpstream), text_fragment,
- kAtTextOffset, Optional<unsigned>(3));
+ kAtTextOffset, base::Optional<unsigned>(3));
}
-TEST_F(NGCaretRectTest, CaretPositionAtSoftLineWrap) {
+TEST_F(NGCaretPositionTest, CaretPositionAtSoftLineWrap) {
SetInlineFormattingContext("t", "foobar", 3);
const Node* text = container_->firstChild();
const auto text_fragments = NGInlineFragmentTraversal::SelfFragmentsOf(
@@ -102,12 +103,12 @@ TEST_F(NGCaretRectTest, CaretPositionAtSoftLineWrap) {
const NGPhysicalFragment* bar_fragment = text_fragments[1].fragment.get();
TEST_CARET(ComputeNGCaretPosition(3, TextAffinity::kDownstream), bar_fragment,
- kAtTextOffset, Optional<unsigned>(3));
+ kAtTextOffset, base::Optional<unsigned>(3));
TEST_CARET(ComputeNGCaretPosition(3, TextAffinity::kUpstream), foo_fragment,
- kAtTextOffset, Optional<unsigned>(3));
+ kAtTextOffset, base::Optional<unsigned>(3));
}
-TEST_F(NGCaretRectTest, CaretPositionAtSoftLineWrapWithSpace) {
+TEST_F(NGCaretPositionTest, CaretPositionAtSoftLineWrapWithSpace) {
SetInlineFormattingContext("t", "foo bar", 3);
const Node* text = container_->firstChild();
const auto text_fragments = NGInlineFragmentTraversal::SelfFragmentsOf(
@@ -117,18 +118,18 @@ TEST_F(NGCaretRectTest, CaretPositionAtSoftLineWrapWithSpace) {
// Before the space
TEST_CARET(ComputeNGCaretPosition(3, TextAffinity::kDownstream), foo_fragment,
- kAtTextOffset, Optional<unsigned>(3));
+ kAtTextOffset, base::Optional<unsigned>(3));
TEST_CARET(ComputeNGCaretPosition(3, TextAffinity::kUpstream), foo_fragment,
- kAtTextOffset, Optional<unsigned>(3));
+ kAtTextOffset, base::Optional<unsigned>(3));
// After the space
TEST_CARET(ComputeNGCaretPosition(4, TextAffinity::kDownstream), bar_fragment,
- kAtTextOffset, Optional<unsigned>(4));
+ kAtTextOffset, base::Optional<unsigned>(4));
TEST_CARET(ComputeNGCaretPosition(4, TextAffinity::kUpstream), bar_fragment,
- kAtTextOffset, Optional<unsigned>(4));
+ kAtTextOffset, base::Optional<unsigned>(4));
}
-TEST_F(NGCaretRectTest, CaretPositionAtForcedLineBreak) {
+TEST_F(NGCaretPositionTest, CaretPositionAtForcedLineBreak) {
SetInlineFormattingContext("t", "foo<br>bar", 3);
const Node* foo = container_->firstChild();
const Node* br = foo->nextSibling();
@@ -138,18 +139,18 @@ TEST_F(NGCaretRectTest, CaretPositionAtForcedLineBreak) {
// Before the BR
TEST_CARET(ComputeNGCaretPosition(3, TextAffinity::kDownstream), foo_fragment,
- kAtTextOffset, Optional<unsigned>(3));
+ kAtTextOffset, base::Optional<unsigned>(3));
TEST_CARET(ComputeNGCaretPosition(3, TextAffinity::kUpstream), foo_fragment,
- kAtTextOffset, Optional<unsigned>(3));
+ kAtTextOffset, base::Optional<unsigned>(3));
// After the BR
TEST_CARET(ComputeNGCaretPosition(4, TextAffinity::kDownstream), bar_fragment,
- kAtTextOffset, Optional<unsigned>(4));
+ kAtTextOffset, base::Optional<unsigned>(4));
TEST_CARET(ComputeNGCaretPosition(4, TextAffinity::kUpstream), bar_fragment,
- kAtTextOffset, Optional<unsigned>(4));
+ kAtTextOffset, base::Optional<unsigned>(4));
}
-TEST_F(NGCaretRectTest, CaretPositionAtEmptyLine) {
+TEST_F(NGCaretPositionTest, CaretPositionAtEmptyLine) {
SetInlineFormattingContext("f", "foo<br><br>bar", 3);
const Node* foo = container_->firstChild();
const Node* br1 = foo->nextSibling();
@@ -157,30 +158,30 @@ TEST_F(NGCaretRectTest, CaretPositionAtEmptyLine) {
const NGPhysicalFragment* br2_fragment = FragmentOf(br2);
TEST_CARET(ComputeNGCaretPosition(4, TextAffinity::kDownstream), br2_fragment,
- kAtTextOffset, Optional<unsigned>(4));
+ kAtTextOffset, base::Optional<unsigned>(4));
TEST_CARET(ComputeNGCaretPosition(4, TextAffinity::kUpstream), br2_fragment,
- kAtTextOffset, Optional<unsigned>(4));
+ kAtTextOffset, base::Optional<unsigned>(4));
}
-TEST_F(NGCaretRectTest, CaretPositionInOneLineOfImage) {
+TEST_F(NGCaretPositionTest, CaretPositionInOneLineOfImage) {
SetInlineFormattingContext("t", "<img>", 3);
const Node* img = container_->firstChild();
const NGPhysicalFragment* img_fragment = FragmentOf(img);
// Before the image
TEST_CARET(ComputeNGCaretPosition(0, TextAffinity::kDownstream), img_fragment,
- kBeforeBox, WTF::nullopt);
+ kBeforeBox, base::nullopt);
TEST_CARET(ComputeNGCaretPosition(0, TextAffinity::kUpstream), img_fragment,
- kBeforeBox, WTF::nullopt);
+ kBeforeBox, base::nullopt);
// After the image
TEST_CARET(ComputeNGCaretPosition(1, TextAffinity::kDownstream), img_fragment,
- kAfterBox, WTF::nullopt);
+ kAfterBox, base::nullopt);
TEST_CARET(ComputeNGCaretPosition(1, TextAffinity::kUpstream), img_fragment,
- kAfterBox, WTF::nullopt);
+ kAfterBox, base::nullopt);
}
-TEST_F(NGCaretRectTest, CaretPositionAtSoftLineWrapBetweenImages) {
+TEST_F(NGCaretPositionTest, CaretPositionAtSoftLineWrapBetweenImages) {
SetInlineFormattingContext("t",
"<img id=img1><img id=img2>"
"<style>img{width: 1em; height: 1em}</style>",
@@ -191,12 +192,13 @@ TEST_F(NGCaretRectTest, CaretPositionAtSoftLineWrapBetweenImages) {
const NGPhysicalFragment* img2_fragment = FragmentOf(img2);
TEST_CARET(ComputeNGCaretPosition(1, TextAffinity::kDownstream),
- img2_fragment, kBeforeBox, WTF::nullopt);
+ img2_fragment, kBeforeBox, base::nullopt);
TEST_CARET(ComputeNGCaretPosition(1, TextAffinity::kUpstream), img1_fragment,
- kAfterBox, WTF::nullopt);
+ kAfterBox, base::nullopt);
}
-TEST_F(NGCaretRectTest, CaretPositionAtSoftLineWrapBetweenMultipleTextNodes) {
+TEST_F(NGCaretPositionTest,
+ CaretPositionAtSoftLineWrapBetweenMultipleTextNodes) {
SetInlineFormattingContext("t",
"<span>A</span>"
"<span>B</span>"
@@ -216,12 +218,12 @@ TEST_F(NGCaretRectTest, CaretPositionAtSoftLineWrapBetweenMultipleTextNodes) {
mapping.GetTextContentOffset(wrap_position).value();
TEST_CARET(ComputeNGCaretPosition(wrap_offset, TextAffinity::kUpstream),
- fragment_c, kAtTextOffset, Optional<unsigned>(wrap_offset));
+ fragment_c, kAtTextOffset, base::Optional<unsigned>(wrap_offset));
TEST_CARET(ComputeNGCaretPosition(wrap_offset, TextAffinity::kDownstream),
- fragment_d, kAtTextOffset, Optional<unsigned>(wrap_offset));
+ fragment_d, kAtTextOffset, base::Optional<unsigned>(wrap_offset));
}
-TEST_F(NGCaretRectTest,
+TEST_F(NGCaretPositionTest,
CaretPositionAtSoftLineWrapBetweenMultipleTextNodesRtl) {
SetInlineFormattingContext("t",
"<span>A</span>"
@@ -242,12 +244,12 @@ TEST_F(NGCaretRectTest,
mapping.GetTextContentOffset(wrap_position).value();
TEST_CARET(ComputeNGCaretPosition(wrap_offset, TextAffinity::kUpstream),
- fragment_c, kAtTextOffset, Optional<unsigned>(wrap_offset));
+ fragment_c, kAtTextOffset, base::Optional<unsigned>(wrap_offset));
TEST_CARET(ComputeNGCaretPosition(wrap_offset, TextAffinity::kDownstream),
- fragment_d, kAtTextOffset, Optional<unsigned>(wrap_offset));
+ fragment_d, kAtTextOffset, base::Optional<unsigned>(wrap_offset));
}
-TEST_F(NGCaretRectTest, CaretPositionAtSoftLineWrapBetweenDeepTextNodes) {
+TEST_F(NGCaretPositionTest, CaretPositionAtSoftLineWrapBetweenDeepTextNodes) {
SetInlineFormattingContext(
"t",
"<style>span {border: 1px solid black}</style>"
@@ -269,9 +271,9 @@ TEST_F(NGCaretRectTest, CaretPositionAtSoftLineWrapBetweenDeepTextNodes) {
mapping.GetTextContentOffset(wrap_position).value();
TEST_CARET(ComputeNGCaretPosition(wrap_offset, TextAffinity::kUpstream),
- fragment_c, kAtTextOffset, Optional<unsigned>(wrap_offset));
+ fragment_c, kAtTextOffset, base::Optional<unsigned>(wrap_offset));
TEST_CARET(ComputeNGCaretPosition(wrap_offset, TextAffinity::kDownstream),
- fragment_d, kAtTextOffset, Optional<unsigned>(wrap_offset));
+ fragment_d, kAtTextOffset, base::Optional<unsigned>(wrap_offset));
}
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc
index 7b009be5ee1..b0e7e25ba52 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc
@@ -5,187 +5,18 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.h"
#include "third_party/blink/renderer/core/editing/local_caret_rect.h"
-#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
+#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
-#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
-#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment_traversal.h"
-#include "third_party/blink/renderer/platform/fonts/character_range.h"
namespace blink {
namespace {
-// -------------------------------------
-
-// Caret position calculation and its helpers.
-
-// The calculation takes the following input:
-// - An inline formatting context as a |LayoutBlockFlow|
-// - An offset in the |text_content_| string of the above context
-// - A TextAffinity
-//
-// The calculation iterates all inline fragments in the context, and tries to
-// compute an NGCaretPosition using the "caret resolution process" below:
-//
-// The (offset, affinity) pair is compared against each inline fragment to see
-// if the corresponding caret should be placed in the fragment, using the
-// |TryResolveCaretPositionInXXX()| functions. These functions may return:
-// - Failed, indicating that the caret must not be placed in the fragment;
-// - Resolved, indicating that the care should be placed in the fragment, and
-// no further search is required. The result NGCaretPosition is returned
-// together.
-// - FoundCandidate, indicating that the caret may be placed in the fragment;
-// however, further search may find a better position. The candidate
-// NGCaretPosition is also returned together.
-
-enum class ResolutionType { kFailed, kFoundCandidate, kResolved };
-struct CaretPositionResolution {
- ResolutionType type = ResolutionType::kFailed;
- NGCaretPosition caret_position;
-};
-
-bool CanResolveCaretPositionBeforeFragment(const NGPaintFragment& fragment,
- TextAffinity affinity) {
- if (affinity == TextAffinity::kDownstream)
- return true;
- const NGPaintFragment* current_line_paint = fragment.ContainerLineBox();
- const NGPhysicalLineBoxFragment& current_line =
- ToNGPhysicalLineBoxFragment(current_line_paint->PhysicalFragment());
- // A fragment after line wrap must be the first logical leaf in its line.
- if (&fragment.PhysicalFragment() != current_line.FirstLogicalLeaf())
- return true;
- const NGPaintFragment* last_line_paint =
- NGPaintFragmentTraversal::PreviousLineOf(*current_line_paint);
- return !last_line_paint ||
- !ToNGPhysicalLineBoxFragment(last_line_paint->PhysicalFragment())
- .HasSoftWrapToNextLine();
-}
-
-bool CanResolveCaretPositionAfterFragment(const NGPaintFragment& fragment,
- TextAffinity affinity) {
- if (affinity == TextAffinity::kUpstream)
- return true;
- const NGPaintFragment* current_line_paint = fragment.ContainerLineBox();
- const NGPhysicalLineBoxFragment& current_line =
- ToNGPhysicalLineBoxFragment(current_line_paint->PhysicalFragment());
- // A fragment before line wrap must be the last logical leaf in its line.
- if (&fragment.PhysicalFragment() != current_line.LastLogicalLeaf())
- return true;
- return !current_line.HasSoftWrapToNextLine();
-}
-
-CaretPositionResolution TryResolveCaretPositionInTextFragment(
- const NGPaintFragment& paint_fragment,
- unsigned offset,
- TextAffinity affinity) {
- DCHECK(paint_fragment.PhysicalFragment().IsText());
- const NGPhysicalTextFragment& fragment =
- ToNGPhysicalTextFragment(paint_fragment.PhysicalFragment());
- if (fragment.IsAnonymousText())
- return CaretPositionResolution();
-
- // [StartOffset(), EndOffset()] is the range allowing caret placement.
- // For example, "foo" has 4 offsets allowing caret placement.
- if (offset < fragment.StartOffset() || offset > fragment.EndOffset()) {
- // TODO(xiaochengh): This may introduce false negatives. Investigate.
- return CaretPositionResolution();
- }
- NGCaretPosition candidate = {&paint_fragment,
- NGCaretPositionType::kAtTextOffset, offset};
-
- // Offsets in the interior of a fragment can be resolved directly.
- if (offset > fragment.StartOffset() && offset < fragment.EndOffset())
- return {ResolutionType::kResolved, candidate};
-
- if (offset == fragment.StartOffset() &&
- CanResolveCaretPositionBeforeFragment(paint_fragment, affinity)) {
- return {ResolutionType::kResolved, candidate};
- }
-
- if (offset == fragment.EndOffset() && !fragment.IsLineBreak() &&
- CanResolveCaretPositionAfterFragment(paint_fragment, affinity)) {
- return {ResolutionType::kResolved, candidate};
- }
-
- // We may have a better candidate
- return {ResolutionType::kFoundCandidate, candidate};
-}
-
-unsigned GetTextOffsetBefore(const NGPhysicalFragment& fragment) {
- // TODO(xiaochengh): Design more straightforward way to get text offset of
- // atomic inline box.
- DCHECK(fragment.IsAtomicInline());
- const Node* node = fragment.GetNode();
- DCHECK(node);
- const Position before_node = Position::BeforeNode(*node);
- Optional<unsigned> maybe_offset_before =
- NGOffsetMapping::GetFor(before_node)->GetTextContentOffset(before_node);
- // We should have offset mapping for atomic inline boxes.
- DCHECK(maybe_offset_before.has_value());
- return maybe_offset_before.value();
-}
-
-CaretPositionResolution TryResolveCaretPositionByBoxFragmentSide(
- const NGPaintFragment& fragment,
- unsigned offset,
- TextAffinity affinity) {
- if (!fragment.GetNode()) {
- // TODO(xiaochengh): This leads to false negatives for, e.g., RUBY, where an
- // anonymous wrapping inline block is created.
- return CaretPositionResolution();
- }
-
- const unsigned offset_before =
- GetTextOffsetBefore(fragment.PhysicalFragment());
- const unsigned offset_after = offset_before + 1;
- if (offset != offset_before && offset != offset_after)
- return CaretPositionResolution();
- const NGCaretPositionType position_type =
- offset == offset_before ? NGCaretPositionType::kBeforeBox
- : NGCaretPositionType::kAfterBox;
- NGCaretPosition candidate{&fragment, position_type, WTF::nullopt};
-
- if (offset == offset_before &&
- CanResolveCaretPositionBeforeFragment(fragment, affinity)) {
- return {ResolutionType::kResolved, candidate};
- }
-
- if (offset == offset_after &&
- CanResolveCaretPositionAfterFragment(fragment, affinity)) {
- return {ResolutionType::kResolved, candidate};
- }
-
- return {ResolutionType::kFoundCandidate, candidate};
-}
-
-CaretPositionResolution TryResolveCaretPositionWithFragment(
- const NGPaintFragment& paint_fragment,
- unsigned offset,
- TextAffinity affinity) {
- const NGPhysicalFragment& fragment = paint_fragment.PhysicalFragment();
- if (fragment.IsText()) {
- return TryResolveCaretPositionInTextFragment(paint_fragment, offset,
- affinity);
- }
- if (fragment.IsBox() && fragment.IsAtomicInline()) {
- return TryResolveCaretPositionByBoxFragmentSide(paint_fragment, offset,
- affinity);
- }
- return CaretPositionResolution();
-}
-
-// -------------------------------------
-
-// Helpers for converting NGCaretPositions to caret rects.
-
NGPhysicalOffsetRect ComputeLocalCaretRectByBoxSide(
- const LayoutBlockFlow& context,
const NGPaintFragment& fragment,
NGCaretPositionType position_type) {
const bool is_horizontal = fragment.Style().IsHorizontalWritingMode();
@@ -203,7 +34,7 @@ NGPhysicalOffsetRect ComputeLocalCaretRectByBoxSide(
fragment.GetLayoutObject()->GetDocument().View();
LayoutUnit caret_width = frame_view->CaretWidth();
- const bool is_ltr = fragment.Style().Direction() == TextDirection::kLtr;
+ const bool is_ltr = IsLtr(fragment.PhysicalFragment().ResolvedDirection());
LayoutUnit caret_left;
if (is_ltr != (position_type == NGCaretPositionType::kBeforeBox)) {
if (is_horizontal)
@@ -223,7 +54,6 @@ NGPhysicalOffsetRect ComputeLocalCaretRectByBoxSide(
}
NGPhysicalOffsetRect ComputeLocalCaretRectAtTextOffset(
- const LayoutBlockFlow& context,
const NGPaintFragment& paint_fragment,
unsigned offset) {
const NGPhysicalTextFragment& fragment =
@@ -255,7 +85,8 @@ NGPhysicalOffsetRect ComputeLocalCaretRectAtTextOffset(
paint_fragment.InlineOffsetToContainerBox();
NGPhysicalSize caret_size(caret_width, caret_height);
- const NGPhysicalBoxFragment& context_fragment = *context.CurrentFragment();
+ const NGPaintFragment& context_fragment =
+ *NGPaintFragment::GetForInlineContainer(fragment.GetLayoutObject());
const NGPaintFragment* line_box = paint_fragment.ContainerLineBox();
const NGPhysicalOffset line_box_offset =
line_box->InlineOffsetToContainerBox();
@@ -284,101 +115,48 @@ NGPhysicalOffsetRect ComputeLocalCaretRectAtTextOffset(
return NGPhysicalOffsetRect(caret_location, caret_size);
}
-LocalCaretRect ComputeLocalCaretRect(const LayoutBlockFlow& context,
- const NGCaretPosition& caret_position) {
+LocalCaretRect ComputeLocalCaretRect(const NGCaretPosition& caret_position) {
if (caret_position.IsNull())
return LocalCaretRect();
+ const NGPaintFragment& fragment = *caret_position.fragment;
+ const LayoutObject* layout_object = fragment.GetLayoutObject();
switch (caret_position.position_type) {
case NGCaretPositionType::kBeforeBox:
case NGCaretPositionType::kAfterBox: {
- DCHECK(caret_position.fragment->PhysicalFragment().IsBox());
+ DCHECK(fragment.PhysicalFragment().IsBox());
const NGPhysicalOffsetRect fragment_local_rect =
- ComputeLocalCaretRectByBoxSide(context, *caret_position.fragment,
+ ComputeLocalCaretRectByBoxSide(fragment,
caret_position.position_type);
- return {caret_position.fragment->GetLayoutObject(),
- fragment_local_rect.ToLayoutRect()};
+ return {layout_object, fragment_local_rect.ToLayoutRect()};
}
case NGCaretPositionType::kAtTextOffset: {
- DCHECK(caret_position.fragment->PhysicalFragment().IsText());
+ DCHECK(fragment.PhysicalFragment().IsText());
DCHECK(caret_position.text_offset.has_value());
const NGPhysicalOffsetRect caret_rect = ComputeLocalCaretRectAtTextOffset(
- context, *caret_position.fragment, *caret_position.text_offset);
-
- return {caret_position.fragment->GetLayoutObject(),
- caret_rect.ToLayoutRect()};
+ fragment, *caret_position.text_offset);
+ LayoutRect layout_rect = caret_rect.ToLayoutRect();
+
+ // For vertical-rl, convert to "flipped block-flow" coordinates space.
+ // See core/layout/README.md#coordinate-spaces for details.
+ if (fragment.Style().IsFlippedBlocksWritingMode()) {
+ const LayoutBlockFlow* container =
+ layout_object->EnclosingNGBlockFlow();
+ container->FlipForWritingMode(layout_rect);
+ }
+
+ return {layout_object, layout_rect};
}
}
NOTREACHED();
- return {caret_position.fragment->GetLayoutObject(), LayoutRect()};
-}
-
-// -------------------------------------
-
-void AssertValidPositionForCaretRectComputation(
- const PositionWithAffinity& position) {
-#if DCHECK_IS_ON()
- DCHECK(NGOffsetMapping::AcceptsPosition(position.GetPosition()));
- const LayoutObject* layout_object = position.AnchorNode()->GetLayoutObject();
- DCHECK(layout_object);
- DCHECK(layout_object->IsText() || layout_object->IsAtomicInlineLevel());
-#endif
+ return {layout_object, LayoutRect()};
}
} // namespace
-// The main function for compute an NGCaretPosition. See the comments at the top
-// of this file for details.
-NGCaretPosition ComputeNGCaretPosition(const LayoutBlockFlow& context,
- unsigned offset,
- TextAffinity affinity) {
- const NGPaintFragment* root_fragment = context.PaintFragment();
- DCHECK(root_fragment);
-
- NGCaretPosition candidate;
- for (const auto& child :
- NGPaintFragmentTraversal::InlineDescendantsOf(*root_fragment)) {
- const CaretPositionResolution resolution =
- TryResolveCaretPositionWithFragment(*child.fragment, offset, affinity);
-
- if (resolution.type == ResolutionType::kFailed)
- continue;
-
- // TODO(xiaochengh): Handle caret poisition in empty container (e.g. empty
- // line box).
-
- if (resolution.type == ResolutionType::kResolved)
- return resolution.caret_position;
-
- DCHECK_EQ(ResolutionType::kFoundCandidate, resolution.type);
- // TODO(xiaochengh): We are not sure if we can ever find multiple
- // candidates. Handle it once reached.
- DCHECK(candidate.IsNull());
- candidate = resolution.caret_position;
- }
-
- return candidate;
-}
-
-LocalCaretRect ComputeNGLocalCaretRect(const LayoutBlockFlow& context,
- const PositionWithAffinity& position) {
- AssertValidPositionForCaretRectComputation(position);
- DCHECK_EQ(&context, NGInlineFormattingContextOf(position.GetPosition()));
- const NGOffsetMapping* mapping = NGOffsetMapping::GetFor(&context);
- DCHECK(mapping);
- const Optional<unsigned> maybe_offset =
- mapping->GetTextContentOffset(position.GetPosition());
- if (!maybe_offset.has_value()) {
- // TODO(xiaochengh): Investigate if we reach here.
- NOTREACHED();
- return LocalCaretRect();
- }
-
- const unsigned offset = maybe_offset.value();
- const TextAffinity affinity = position.Affinity();
- return ComputeLocalCaretRect(
- context, ComputeNGCaretPosition(context, offset, affinity));
+LocalCaretRect ComputeNGLocalCaretRect(const PositionWithAffinity& position) {
+ return ComputeLocalCaretRect(ComputeNGCaretPosition(position));
}
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.h
index 2268a8220c8..4215ef0d6f5 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.h
@@ -5,53 +5,20 @@
#ifndef NGCaretRect_h
#define NGCaretRect_h
+#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/editing/forward.h"
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h"
-#include "third_party/blink/renderer/platform/wtf/forward.h"
-#include "third_party/blink/renderer/platform/wtf/optional.h"
namespace blink {
// This file provides utility functions for computing caret rect in LayoutNG.
-class NGPaintFragment;
-class LayoutBlockFlow;
struct LocalCaretRect;
-// Given an inline formatting context and a position in the context, returns the
-// caret rect if a caret should be placed at the position, with the given
-// affinity. The caret rect location is local to the given formatting context.
-CORE_EXPORT LocalCaretRect ComputeNGLocalCaretRect(const LayoutBlockFlow&,
- const PositionWithAffinity&);
-
-// An NGCaretPosition indicates a caret position relative to an inline
-// NGPaintFragment:
-// - When |fragment| is box, |position_type| is either |kBeforeBox| or
-// |kAfterBox|, indicating either of the two caret positions by the box sides;
-// |text_offset| is |nullopt| in this case.
-// - When |fragment| is text, |position_type| is |kAtTextOffset|, and
-// |text_offset| is in the text offset range of the fragment.
-//
-// TODO(xiaochengh): Support "in empty container" caret type
-
-enum class NGCaretPositionType { kBeforeBox, kAfterBox, kAtTextOffset };
-struct NGCaretPosition {
- DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
-
- bool IsNull() const { return !fragment; }
-
- const NGPaintFragment* fragment = nullptr; // owned by root LayoutNGMixin
- NGCaretPositionType position_type;
- Optional<unsigned> text_offset;
-};
-
-// Given an inline formatting context, a text offset in the context and a text
-// affinity, returns the corresponding NGCaretPosition, or null if not found.
-// Note that in many cases, null result indicates that we have reached an
-// unexpected case that is not properly handled.
-CORE_EXPORT NGCaretPosition ComputeNGCaretPosition(const LayoutBlockFlow&,
- unsigned,
- TextAffinity);
+// Given a position with affinity, returns the caret rect if the position is
+// laid out with LayoutNG, and a caret can be placed at the position with the
+// given affinity. The caret rect location is local to the containing inline
+// formatting context.
+CORE_EXPORT LocalCaretRect ComputeNGLocalCaretRect(const PositionWithAffinity&);
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
index 3cffb0aab2c..fca16e8c347 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
@@ -274,10 +274,13 @@ void NGInlineLayoutStateStack::AddBoxFragmentPlaceholder(
// Do not defer creating a box fragment if this is an empty inline box.
// An empty box fragment is still flat that we do not have to defer.
// Also, placeholders cannot be reordred if empty.
- scoped_refptr<NGLayoutResult> layout_result =
- box_data.CreateBoxFragment(line_box);
offset.inline_offset += box_data.margin_line_left;
- line_box->AddChild(layout_result, offset, box_data.size.inline_size, 0);
+ LayoutUnit advance = box_data.margin_border_padding_line_left +
+ box_data.margin_border_padding_line_right;
+ box_data.size.inline_size =
+ advance - box_data.margin_line_left - box_data.margin_line_right;
+ line_box->AddChild(box_data.CreateBoxFragment(line_box), offset, advance,
+ 0);
box_data_list_.pop_back();
}
}
@@ -365,55 +368,67 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions(
if (box_data_list_.IsEmpty())
return position;
- // Create box fragments.
+ // Compute inline positions of inline boxes.
for (auto& box_data : box_data_list_) {
unsigned start = box_data.fragment_start;
unsigned end = box_data.fragment_end;
DCHECK_GT(end, start);
NGLineBoxFragmentBuilder::Child& start_child = (*line_box)[start];
+
// Clamping left offset is not defined, match to the existing behavior.
LayoutUnit line_left_offset =
start_child.offset.inline_offset.ClampNegativeToZero();
LayoutUnit line_right_offset = end < line_box->size()
? (*line_box)[end].offset.inline_offset
: position;
- box_data.offset.inline_offset = line_left_offset;
- box_data.size.inline_size = line_right_offset - line_left_offset;
+ box_data.offset.inline_offset =
+ line_left_offset + box_data.margin_line_left;
+ box_data.size.inline_size =
+ line_right_offset - line_left_offset +
+ box_data.margin_border_padding_line_left - box_data.margin_line_left +
+ box_data.margin_border_padding_line_right - box_data.margin_line_right;
+
+ // Adjust child offsets for margin/border/padding.
+ if (box_data.margin_border_padding_line_left) {
+ line_box->MoveInInlineDirection(box_data.margin_border_padding_line_left,
+ start, line_box->size());
+ position += box_data.margin_border_padding_line_left;
+ }
+
+ if (box_data.margin_border_padding_line_right) {
+ line_box->MoveInInlineDirection(box_data.margin_border_padding_line_right,
+ end, line_box->size());
+ position += box_data.margin_border_padding_line_right;
+ }
+ }
+
+ return position;
+}
+
+void NGInlineLayoutStateStack::CreateBoxFragments(
+ NGLineBoxFragmentBuilder::ChildList* line_box) {
+ DCHECK(!box_data_list_.IsEmpty());
+
+ for (auto& box_data : box_data_list_) {
+ unsigned start = box_data.fragment_start;
+ unsigned end = box_data.fragment_end;
+ DCHECK_GT(end, start);
+ NGLineBoxFragmentBuilder::Child& start_child = (*line_box)[start];
scoped_refptr<NGLayoutResult> box_fragment =
box_data.CreateBoxFragment(line_box);
- NGLogicalOffset offset(line_left_offset + box_data.margin_line_left,
- box_data.offset.block_offset);
if (!start_child.HasFragment()) {
start_child.layout_result = std::move(box_fragment);
- start_child.offset = offset;
+ start_child.offset = box_data.offset;
} 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.
- line_box->InsertChild(start, std::move(box_fragment), offset,
+ line_box->InsertChild(start, std::move(box_fragment), box_data.offset,
LayoutUnit(), 0);
}
-
- // Out-of-flow fragments are left in (start + 1, end). Move them by the left
- // margin/border/padding.
- if (box_data.margin_border_padding_line_left) {
- line_box->MoveInInlineDirection(box_data.margin_border_padding_line_left,
- start + 1, end);
- }
- // Move the rest of children by the inline size the box consumes.
- LayoutUnit margin_border_padding =
- box_data.margin_border_padding_line_left +
- box_data.margin_border_padding_line_right;
- if (margin_border_padding) {
- line_box->MoveInInlineDirection(margin_border_padding, end,
- line_box->size());
- position += margin_border_padding;
- }
}
box_data_list_.clear();
-
- return position;
}
scoped_refptr<NGLayoutResult>
@@ -427,17 +442,12 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment(
NGFragmentBuilder box(item->GetLayoutObject(), &style, style.GetWritingMode(),
TextDirection::kLtr);
box.SetBoxType(NGPhysicalFragment::kInlineBox);
+ box.SetStyleVariant(item->StyleVariant());
// Inline boxes have block start/end borders, even when its containing block
// was fragmented. Fragmenting a line box in block direction is not
// supported today.
box.SetBorderEdges({true, has_line_right_edge, true, has_line_left_edge});
- LayoutUnit border_padding_line_left =
- margin_border_padding_line_left - margin_line_left;
- LayoutUnit border_padding_line_right =
- margin_border_padding_line_right - margin_line_right;
- offset.inline_offset -= border_padding_line_left;
- size.inline_size += border_padding_line_left + border_padding_line_right;
box.SetInlineSize(size.inline_size.ClampNegativeToZero());
box.SetBlockSize(size.block_size);
box.SetPadding(padding);
@@ -462,7 +472,9 @@ NGInlineLayoutStateStack::ApplyBaselineShift(
NGInlineBoxState* box,
NGLineBoxFragmentBuilder::ChildList* line_box,
FontBaseline baseline_type) {
- // Compute descendants that depend on the layout size of this box if any.
+ // Some 'vertical-align' values require the size of their parents. Align all
+ // such descendant boxes that require the size of this box; they are queued in
+ // |pending_descendants|.
LayoutUnit baseline_shift;
if (!box->pending_descendants.IsEmpty()) {
for (auto& child : box->pending_descendants) {
@@ -514,9 +526,15 @@ NGInlineLayoutStateStack::ApplyBaselineShift(
if (vertical_align == EVerticalAlign::kBaseline)
return kPositionNotPending;
- // 'vertical-align' aplies only to inline-level elements.
+ // 'vertical-align' aligns boxes relative to themselves, to their parent
+ // boxes, or to the line box, depends on the value.
+ // Because |box| is an item in |stack_|, |box[-1]| is its parent box.
+ // If this box doesn't have a parent; i.e., this box is a line box,
+ // 'vertical-align' has no effect.
+ DCHECK(box >= stack_.begin() && box < stack_.end());
if (box == stack_.begin())
return kPositionNotPending;
+ NGInlineBoxState& parent_box = box[-1];
// Check if there are any fragments to move.
unsigned fragment_end = line_box->size();
@@ -525,10 +543,10 @@ NGInlineLayoutStateStack::ApplyBaselineShift(
switch (vertical_align) {
case EVerticalAlign::kSub:
- baseline_shift = style.ComputedFontSizeAsFixed() / 5 + 1;
+ baseline_shift = parent_box.style->ComputedFontSizeAsFixed() / 5 + 1;
break;
case EVerticalAlign::kSuper:
- baseline_shift = -(style.ComputedFontSizeAsFixed() / 3 + 1);
+ baseline_shift = -(parent_box.style->ComputedFontSizeAsFixed() / 3 + 1);
break;
case EVerticalAlign::kLength: {
// 'Percentages: refer to the 'line-height' of the element itself'.
@@ -542,9 +560,10 @@ NGInlineLayoutStateStack::ApplyBaselineShift(
}
case EVerticalAlign::kMiddle:
baseline_shift = (box->metrics.ascent - box->metrics.descent) / 2;
- if (const SimpleFontData* font_data = style.GetFont().PrimaryFont()) {
+ if (const SimpleFontData* parent_font_data =
+ parent_box.style->GetFont().PrimaryFont()) {
baseline_shift -= LayoutUnit::FromFloatRound(
- font_data->GetFontMetrics().XHeight() / 2);
+ parent_font_data->GetFontMetrics().XHeight() / 2);
}
break;
case EVerticalAlign::kBaselineMiddle:
@@ -558,8 +577,7 @@ NGInlineLayoutStateStack::ApplyBaselineShift(
return kPositionPending;
default:
// Other values require the layout size of the parent box.
- SECURITY_CHECK(box != stack_.begin());
- box[-1].pending_descendants.push_back(NGPendingPositions{
+ parent_box.pending_descendants.push_back(NGPendingPositions{
box->fragment_start, fragment_end, box->metrics, vertical_align});
return kPositionPending;
}
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
index 68fd6c23673..626ad129d87 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
@@ -5,7 +5,6 @@
#ifndef NGInlineBoxState_h
#define NGInlineBoxState_h
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_border_edges.h"
#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.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"
@@ -140,10 +139,13 @@ class CORE_EXPORT NGInlineLayoutStateStack {
// reordering.
void UpdateAfterReorder(NGLineBoxFragmentBuilder::ChildList*);
- // Compute inline positions of fragments. Also creates box fragments if
- // needed.
+ // Compute inline positions of fragments and boxes.
LayoutUnit ComputeInlinePositions(NGLineBoxFragmentBuilder::ChildList*);
+ // Create box fragments. This function turns a flat list of children into
+ // a box tree.
+ void CreateBoxFragments(NGLineBoxFragmentBuilder::ChildList*);
+
private:
// End of a box state, either explicitly by close tag, or implicitly at the
// end of a line.
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc
index 870af1c1802..353acbabc68 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc
@@ -24,6 +24,9 @@ void CollectInlineFragments(const NGPhysicalContainerFragment& container,
NGPhysicalOffset offset_to_container_box,
Filter& filter,
Vector<Result, inline_capacity>* results) {
+ DCHECK(container.IsInline() || container.IsLineBox() ||
+ (container.IsBlockFlow() &&
+ ToNGPhysicalBoxFragment(container).ChildrenInline()));
for (const auto& child : container.Children()) {
NGPhysicalOffset child_offset = child->Offset() + offset_to_container_box;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc
index fe68677cdc2..40bf8c8431a 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc
@@ -64,7 +64,7 @@ TEST_F(NGInlineFragmentTraversalTest, DescendantsOf) {
"<div id=t>foo<b id=b>bar</b><br>baz</div>");
const auto descendants =
NGInlineFragmentTraversal::DescendantsOf(GetRootFragmentById("t"));
- auto iter = descendants.begin();
+ auto* iter = descendants.begin();
EXPECT_NEXT_LINE_BOX(iter);
EXPECT_NEXT_TEXT(iter, "foo");
@@ -82,7 +82,7 @@ TEST_F(NGInlineFragmentTraversalTest, InclusiveDescendantsOf) {
"<div id=t>foo<b id=b>bar</b><br>baz</div>");
auto descendants = NGInlineFragmentTraversal::InclusiveDescendantsOf(
GetRootFragmentById("t"));
- auto iter = descendants.begin();
+ auto* iter = descendants.begin();
EXPECT_NEXT_BOX(iter, "t");
EXPECT_NEXT_LINE_BOX(iter);
@@ -102,7 +102,7 @@ TEST_F(NGInlineFragmentTraversalTest, SelfFragmentsOf) {
const auto descendants = NGInlineFragmentTraversal::SelfFragmentsOf(
GetRootFragmentById("t"), GetLayoutObjectByElementId("filter"));
- auto iter = descendants.begin();
+ auto* iter = descendants.begin();
// <b> generates two box fragments since its content is in two lines.
EXPECT_NEXT_BOX(iter, "filter");
@@ -123,7 +123,7 @@ TEST_F(NGInlineFragmentTraversalTest, AncestorsOf) {
const NGPhysicalFragment& target =
GetFragmentOfNode(root, GetElementById("target")->firstChild());
auto ancestors = NGInlineFragmentTraversal::AncestorsOf(root, target);
- auto iter = ancestors.begin();
+ auto* iter = ancestors.begin();
EXPECT_NEXT_BOX(iter, "target");
EXPECT_NEXT_BOX(iter, "i");
@@ -143,7 +143,7 @@ TEST_F(NGInlineFragmentTraversalTest, InclusiveAncestorsOf) {
GetFragmentOfNode(root, GetElementById("target")->firstChild());
auto ancestors =
NGInlineFragmentTraversal::InclusiveAncestorsOf(root, target);
- auto iter = ancestors.begin();
+ auto* iter = ancestors.begin();
EXPECT_NEXT_TEXT(iter, "foo");
EXPECT_NEXT_BOX(iter, "target");
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc
index 42fd483de3d..8e5a6288ddf 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc
@@ -7,7 +7,6 @@
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
-#include "third_party/blink/renderer/platform/fonts/character_range.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h"
namespace blink {
@@ -44,7 +43,8 @@ NGInlineItem::NGInlineItem(NGInlineItemType type,
unsigned start,
unsigned end,
const ComputedStyle* style,
- LayoutObject* layout_object)
+ LayoutObject* layout_object,
+ bool end_may_collapse)
: start_offset_(start),
end_offset_(end),
script_(USCRIPT_INVALID_CODE),
@@ -56,11 +56,33 @@ NGInlineItem::NGInlineItem(NGInlineItemType type,
is_empty_item_(false),
should_create_box_fragment_(false),
style_variant_(static_cast<unsigned>(NGStyleVariant::kStandard)),
- end_collapse_type_(kNotCollapsible) {
+ end_collapse_type_(kNotCollapsible),
+ end_may_collapse_(end_may_collapse) {
DCHECK_GE(end, start);
ComputeBoxProperties();
}
+NGInlineItem::NGInlineItem(const NGInlineItem& other,
+ unsigned start,
+ unsigned end,
+ scoped_refptr<const ShapeResult> shape_result)
+ : start_offset_(start),
+ end_offset_(end),
+ script_(other.script_),
+ shape_result_(shape_result),
+ style_(other.style_),
+ layout_object_(other.layout_object_),
+ type_(other.type_),
+ bidi_level_(other.bidi_level_),
+ shape_options_(other.shape_options_),
+ is_empty_item_(other.is_empty_item_),
+ should_create_box_fragment_(other.should_create_box_fragment_),
+ style_variant_(other.style_variant_),
+ end_collapse_type_(other.end_collapse_type_),
+ end_may_collapse_(other.end_may_collapse_) {
+ DCHECK_GE(end, start);
+}
+
NGInlineItem::~NGInlineItem() = default;
void NGInlineItem::ComputeBoxProperties() {
@@ -82,7 +104,7 @@ void NGInlineItem::ComputeBoxProperties() {
should_create_box_fragment_ =
ToLayoutBoxModelObject(layout_object_)->HasSelfPaintingLayer() ||
style_->HasOutline() || style_->CanContainAbsolutePositionObjects() ||
- style_->CanContainFixedPositionObjects();
+ style_->CanContainFixedPositionObjects(false);
}
return;
}
@@ -99,6 +121,13 @@ const char* NGInlineItem::NGInlineItemTypeToString(int val) const {
return kNGInlineItemTypeStrings[val];
}
+void NGInlineItem::SetBidiLevel(UBiDiLevel level) {
+ // Invalidate ShapeResult because it depends on the resolved direction.
+ if (DirectionFromLevel(level) != DirectionFromLevel(bidi_level_))
+ shape_result_ = nullptr;
+ bidi_level_ = level;
+}
+
// Set bidi level to a list of NGInlineItem from |index| to the item that ends
// with |end_offset|.
// If |end_offset| is mid of an item, the item is split to ensure each item has
@@ -113,14 +142,14 @@ unsigned NGInlineItem::SetBidiLevel(Vector<NGInlineItem>& items,
unsigned end_offset,
UBiDiLevel level) {
for (; items[index].end_offset_ < end_offset; index++)
- items[index].bidi_level_ = level;
- items[index].bidi_level_ = level;
+ items[index].SetBidiLevel(level);
+ items[index].SetBidiLevel(level);
if (items[index].end_offset_ == end_offset) {
// Let close items have the same bidi-level as the previous item.
while (index + 1 < items.size() &&
items[index + 1].Type() == NGInlineItem::kCloseTag) {
- items[++index].bidi_level_ = level;
+ items[++index].SetBidiLevel(level);
}
} else {
Split(items, index, end_offset);
@@ -152,6 +181,7 @@ void NGInlineItem::Split(Vector<NGInlineItem>& items,
unsigned offset) {
DCHECK_GT(offset, items[index].start_offset_);
DCHECK_LT(offset, items[index].end_offset_);
+ items[index].shape_result_ = nullptr;
items.insert(index + 1, items[index]);
items[index].end_offset_ = offset;
items[index + 1].start_offset_ = offset;
@@ -161,11 +191,15 @@ void NGInlineItem::SetOffset(unsigned start, unsigned end) {
DCHECK_GE(end, start);
start_offset_ = start;
end_offset_ = end;
+ // Any modification to the offset will invalidate the shape result.
+ shape_result_ = nullptr;
}
void NGInlineItem::SetEndOffset(unsigned end_offset) {
DCHECK_GE(end_offset, start_offset_);
end_offset_ = end_offset;
+ // Any modification to the offset will invalidate the shape result.
+ shape_result_ = nullptr;
}
bool NGInlineItem::HasStartEdge() const {
@@ -181,14 +215,4 @@ bool NGInlineItem::HasEndEdge() const {
!ToLayoutInline(GetLayoutObject())->Continuation();
}
-NGInlineItemRange::NGInlineItemRange(Vector<NGInlineItem>* items,
- unsigned start_index,
- unsigned end_index)
- : start_item_(&(*items)[start_index]),
- size_(end_index - start_index),
- start_index_(start_index) {
- CHECK_LE(start_index, end_index);
- CHECK_LE(end_index, items->size());
-}
-
} // 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 d9b183b3e0b..1b40a3a53d7 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
@@ -6,19 +6,17 @@
#define NGInlineItem_h
#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
#include "third_party/blink/renderer/core/layout/ng/ng_style_variant.h"
-#include "third_party/blink/renderer/platform/fonts/font_fallback_priority.h"
+#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
-#include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
-#include "third_party/blink/renderer/platform/layout_unit.h"
#include "third_party/blink/renderer/platform/text/text_direction.h"
-#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include <unicode/ubidi.h>
+#include <unicode/uscript.h>
namespace blink {
-class ComputedStyle;
class LayoutObject;
// Class representing a single text node or styled inline element with text
@@ -66,9 +64,16 @@ class CORE_EXPORT NGInlineItem {
unsigned start,
unsigned end,
const ComputedStyle* style = nullptr,
- LayoutObject* layout_object = nullptr);
+ LayoutObject* layout_object = nullptr,
+ bool end_may_collapse = false);
~NGInlineItem();
+ // Copy constructor adjusting start/end and shape results.
+ NGInlineItem(const NGInlineItem&,
+ unsigned adjusted_start,
+ unsigned adjusted_end,
+ scoped_refptr<const ShapeResult>);
+
NGInlineItemType Type() const { return static_cast<NGInlineItemType>(type_); }
const char* NGInlineItemTypeToString(int val) const;
@@ -118,7 +123,13 @@ class CORE_EXPORT NGInlineItem {
}
void SetEndCollapseType(NGCollapseType type) { end_collapse_type_ = type; }
+ // Whether the item may be affected by whitespace collapsing. Unlike the
+ // EndCollapseType() method this returns true even if a trailing space has
+ // been removed.
+ bool EndMayCollapse() const { return end_may_collapse_; }
+
static void Split(Vector<NGInlineItem>&, unsigned index, unsigned offset);
+ void SetBidiLevel(UBiDiLevel);
static unsigned SetBidiLevel(Vector<NGInlineItem>&,
unsigned index,
unsigned end_offset,
@@ -146,6 +157,7 @@ class CORE_EXPORT NGInlineItem {
unsigned should_create_box_fragment_ : 1;
unsigned style_variant_ : 2;
unsigned end_collapse_type_ : 2; // NGCollapseType
+ unsigned end_may_collapse_ : 1;
friend class NGInlineNode;
};
@@ -159,41 +171,23 @@ inline void NGInlineItem::AssertEndOffset(unsigned offset) const {
DCHECK_LE(offset, end_offset_);
}
-// A vector-like object that points to a subset of an array of |NGInlineItem|.
-// The source vector must keep alive and must not resize while this object
-// is alive.
-class NGInlineItemRange {
- STACK_ALLOCATED();
-
- public:
- NGInlineItemRange(Vector<NGInlineItem>*,
- unsigned start_index,
- unsigned end_index);
+// Represents a text content with a list of NGInlineItem. A node may have an
+// additional NGInlineItemsData for ::first-line pseudo element.
+struct CORE_EXPORT NGInlineItemsData {
+ // Text content for all inline items represented by a single NGInlineNode.
+ // Encoded either as UTF-16 or latin-1 depending on the content.
+ String text_content;
+ Vector<NGInlineItem> items;
- unsigned StartIndex() const { return start_index_; }
- unsigned EndIndex() const { return start_index_ + size_; }
- unsigned Size() const { return size_; }
+ // The DOM to text content offset mapping of this inline node.
+ std::unique_ptr<NGOffsetMapping> offset_mapping;
- NGInlineItem& operator[](unsigned index) {
- CHECK_LT(index, size_);
- return start_item_[index];
+ void AssertOffset(unsigned index, unsigned offset) const {
+ items[index].AssertOffset(offset);
}
- const NGInlineItem& operator[](unsigned index) const {
- CHECK_LT(index, size_);
- return start_item_[index];
+ void AssertEndOffset(unsigned index, unsigned offset) const {
+ items[index].AssertEndOffset(offset);
}
-
- using iterator = NGInlineItem*;
- using const_iterator = const NGInlineItem*;
- iterator begin() { return start_item_; }
- iterator end() { return start_item_ + size_; }
- const_iterator begin() const { return start_item_; }
- const_iterator end() const { return start_item_ + size_; }
-
- private:
- NGInlineItem* start_item_;
- unsigned size_;
- unsigned start_index_;
};
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc
index 74e3e3ca598..43cdd7b803a 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc
@@ -19,14 +19,14 @@ NGInlineItemResult::NGInlineItemResult(const NGInlineItem* item,
: item(item), item_index(index), start_offset(start), end_offset(end) {}
void NGLineInfo::SetLineStyle(const NGInlineNode& node,
+ const NGInlineItemsData& items_data,
const NGConstraintSpace& constraint_space,
bool is_first_line,
+ bool use_first_line_style,
bool is_after_forced_break) {
- LayoutObject* layout_object = node.GetLayoutObject();
- use_first_line_style_ =
- is_first_line &&
- layout_object->GetDocument().GetStyleEngine().UsesFirstLineRules();
- line_style_ = layout_object->Style(use_first_line_style_);
+ use_first_line_style_ = use_first_line_style;
+ items_data_ = &items_data;
+ line_style_ = node.GetLayoutObject()->Style(use_first_line_style_);
if (line_style_->ShouldUseTextIndent(is_first_line, is_after_forced_break)) {
// 'text-indent' applies to block container, and percentage is of its
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h
index 401e52a0a54..514ae11066d 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h
@@ -19,6 +19,8 @@ class NGConstraintSpace;
class NGInlineItem;
class NGInlineNode;
+struct NGInlineItemsData;
+
// The result of measuring NGInlineItem.
//
// This is a transient context object only while building line boxes.
@@ -106,14 +108,21 @@ class CORE_EXPORT NGLineInfo {
NGLineInfo() = default;
explicit NGLineInfo(size_t capacity) : results_(capacity) {}
+ const NGInlineItemsData& ItemsData() const {
+ DCHECK(items_data_);
+ return *items_data_;
+ }
+
// The style to use for the line.
const ComputedStyle& LineStyle() const {
DCHECK(line_style_);
return *line_style_;
}
void SetLineStyle(const NGInlineNode&,
+ const NGInlineItemsData&,
const NGConstraintSpace&,
- bool is_first_line,
+ bool is_first_formatted_line,
+ bool use_first_line_style,
bool is_after_forced_break);
// Use ::first-line style if true.
@@ -158,6 +167,7 @@ class CORE_EXPORT NGLineInfo {
void SetLineEndFragment(scoped_refptr<NGPhysicalTextFragment>);
private:
+ const NGInlineItemsData* items_data_ = nullptr;
const ComputedStyle* line_style_ = nullptr;
NGInlineItemResults results_;
scoped_refptr<NGPhysicalTextFragment> line_end_fragment_;
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 be19e467b59..7fad403e72e 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
@@ -20,27 +20,46 @@ NGInlineItemsBuilderTemplate<
template <typename OffsetMappingBuilder>
String NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::ToString() {
- // Segment Break Transformation Rules[1] defines to keep trailing new lines,
- // but it will be removed in Phase II[2]. We prefer not to add trailing new
- // lines and collapsible spaces in Phase I.
+ // Segment Break Transformation Rules[1] defines to keep trailing new lines in
+ // Phase I, but to remove after line break, in Phase II[2]. Although the spec
+ // defines so, trailing collapsible spaces at the end of an inline formatting
+ // context will be removed in Phase II and that removing here makes no
+ // differences.
+ //
+ // However, doing so reduces the opportunities to re-use NGInlineItem a lot in
+ // appending scenario, which is quite common. In order to re-use NGInlineItem
+ // as much as posssible, trailing spaces are removed in Phase II, exactly as
+ // defined in the spec.
+ //
// [1] https://drafts.csswg.org/css-text-3/#line-break-transform
// [2] https://drafts.csswg.org/css-text-3/#white-space-phase-2
+ return text_.ToString();
+}
+
+template <>
+String NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::ToString() {
+ // While trailing collapsible space is kept as above, NGOffsetMappingBuilder
+ // assumes NGLineBreaker does not remove it. For now, remove only for
+ // NGOffsetMappingBuilder.
+ // TODO(kojii): Consider NGOffsetMappingBuilder to support NGLineBreaker to
+ // remove trailing spaces.
RemoveTrailingCollapsibleSpaceIfExists();
return text_.ToString();
}
+namespace {
// Determine "Ambiguous" East Asian Width is Wide or Narrow.
// Unicode East Asian Width
// http://unicode.org/reports/tr11/
-static bool IsAmbiguosEastAsianWidthWide(const ComputedStyle* style) {
+bool IsAmbiguosEastAsianWidthWide(const ComputedStyle* style) {
UScriptCode script = style->GetFontDescription().GetScript();
return script == USCRIPT_KATAKANA_OR_HIRAGANA ||
script == USCRIPT_SIMPLIFIED_HAN || script == USCRIPT_TRADITIONAL_HAN;
}
// Determine if a character has "Wide" East Asian Width.
-static bool IsEastAsianWidthWide(UChar32 c, const ComputedStyle* style) {
+bool IsEastAsianWidthWide(UChar32 c, const ComputedStyle* style) {
UEastAsianWidth eaw = static_cast<UEastAsianWidth>(
u_getIntPropertyValue(c, UCHAR_EAST_ASIAN_WIDTH));
return eaw == U_EA_WIDE || eaw == U_EA_FULLWIDTH || eaw == U_EA_HALFWIDTH ||
@@ -51,11 +70,11 @@ static bool IsEastAsianWidthWide(UChar32 c, const ComputedStyle* style) {
// Determine whether a newline should be removed or not.
// CSS Text, Segment Break Transformation Rules
// https://drafts.csswg.org/css-text-3/#line-break-transform
-static bool ShouldRemoveNewlineSlow(const StringBuilder& before,
- unsigned space_index,
- const ComputedStyle* before_style,
- const StringView& after,
- const ComputedStyle* after_style) {
+bool ShouldRemoveNewlineSlow(const StringBuilder& before,
+ unsigned space_index,
+ const ComputedStyle* before_style,
+ const StringView& after,
+ const ComputedStyle* after_style) {
// Remove if either before/after the newline is zeroWidthSpaceCharacter.
UChar32 last = 0;
DCHECK(space_index == before.length() ||
@@ -95,27 +114,29 @@ static bool ShouldRemoveNewlineSlow(const StringBuilder& before,
return false;
}
-static bool ShouldRemoveNewline(const StringBuilder& before,
- unsigned space_index,
- const ComputedStyle* before_style,
- const StringView& after,
- const ComputedStyle* after_style) {
+bool ShouldRemoveNewline(const StringBuilder& before,
+ unsigned space_index,
+ const ComputedStyle* before_style,
+ const StringView& after,
+ const ComputedStyle* after_style) {
// All characters before/after removable newline are 16 bits.
return (!before.Is8Bit() || !after.Is8Bit()) &&
ShouldRemoveNewlineSlow(before, space_index, before_style, after,
after_style);
}
-static void AppendItem(Vector<NGInlineItem>* items,
- NGInlineItem::NGInlineItemType type,
- unsigned start,
- unsigned end,
- const ComputedStyle* style = nullptr,
- LayoutObject* layout_object = nullptr) {
- items->push_back(NGInlineItem(type, start, end, style, layout_object));
+void AppendItem(Vector<NGInlineItem>* items,
+ NGInlineItem::NGInlineItemType type,
+ unsigned start,
+ unsigned end,
+ const ComputedStyle* style = nullptr,
+ LayoutObject* layout_object = nullptr,
+ bool end_may_collapse = false) {
+ items->push_back(
+ NGInlineItem(type, start, end, style, layout_object, end_may_collapse));
}
-static inline bool ShouldIgnore(UChar c) {
+inline bool ShouldIgnore(UChar c) {
// Ignore carriage return and form feed.
// https://drafts.csswg.org/css-text-3/#white-space-processing
// https://github.com/w3c/csswg-drafts/issues/855
@@ -126,14 +147,14 @@ static inline bool ShouldIgnore(UChar c) {
return c == kCarriageReturnCharacter || c == kFormFeedCharacter;
}
-static inline bool IsCollapsibleSpace(UChar c) {
+inline bool IsCollapsibleSpace(UChar c) {
return c == kSpaceCharacter || c == kNewlineCharacter ||
c == kTabulationCharacter || c == kCarriageReturnCharacter;
}
// Characters needing a separate control item than other text items.
// It makes the line breaker easier to handle.
-static inline bool IsControlItemCharacter(UChar c) {
+inline bool IsControlItemCharacter(UChar c) {
return c == kNewlineCharacter || c == kTabulationCharacter ||
// Include ignorable character here to avoids shaping/rendering
// these glyphs, and to help the line breaker to ignore them.
@@ -143,9 +164,9 @@ static inline bool IsControlItemCharacter(UChar c) {
// Find the end of the collapsible spaces.
// Returns whether this space run contains a newline or not, because it changes
// the collapsing behavior.
-static inline bool MoveToEndOfCollapsibleSpaces(const StringView& string,
- unsigned* offset,
- UChar* c) {
+inline bool MoveToEndOfCollapsibleSpaces(const StringView& string,
+ unsigned* offset,
+ UChar* c) {
DCHECK_EQ(*c, string[*offset]);
DCHECK(IsCollapsibleSpace(*c));
bool space_run_has_newline = *c == kNewlineCharacter;
@@ -161,7 +182,7 @@ static inline bool MoveToEndOfCollapsibleSpaces(const StringView& string,
// Find the last item to compute collapsing with. Opaque items such as
// open/close or bidi controls are ignored.
// Returns nullptr if there were no previous items.
-static NGInlineItem* LastItemToCollapseWith(Vector<NGInlineItem>* items) {
+NGInlineItem* LastItemToCollapseWith(Vector<NGInlineItem>* items) {
for (auto it = items->rbegin(); it != items->rend(); it++) {
NGInlineItem& item = *it;
if (item.EndCollapseType() != NGInlineItem::kOpaqueToCollapsing)
@@ -170,6 +191,66 @@ static NGInlineItem* LastItemToCollapseWith(Vector<NGInlineItem>* items) {
return nullptr;
}
+inline bool MayCollapseWithLast(const NGInlineItem* item) {
+ return item && item->EndMayCollapse();
+}
+
+} // anonymous namespace
+
+template <typename OffsetMappingBuilder>
+bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::Append(
+ const String& original_string,
+ LayoutObject* layout_object,
+ const Vector<NGInlineItem*>& items) {
+ // Don't reuse existing items if they might be affected by whitespace
+ // collapsing.
+ // TODO(layout-dev): This could likely be optimized further.
+ // TODO(layout-dev): Handle cases where the old items are not consecutive.
+ if (MayCollapseWithLast(LastItemToCollapseWith(items_)) ||
+ IsCollapsibleSpace(original_string[items[0]->StartOffset()]))
+ return false;
+
+ for (const NGInlineItem* item : items) {
+ unsigned start = text_.length();
+ text_.Append(original_string, item->StartOffset(), item->Length());
+
+ // If the item's position within the container remains unchanged the item
+ // itself may be reused.
+ if (item->StartOffset() == start) {
+ items_->push_back(*item);
+ is_empty_inline_ &= item->IsEmptyItem();
+ continue;
+ }
+
+ // If the position has shifted the item and the shape result needs to be
+ // adjusted to reflect the new start and end offsets.
+ unsigned end = start + item->Length();
+ DCHECK(item->TextShapeResult());
+ NGInlineItem adjusted_item(
+ *item, start, end, item->TextShapeResult()->CopyAdjustedOffset(start));
+
+ DCHECK(adjusted_item.TextShapeResult());
+ DCHECK_EQ(start, adjusted_item.StartOffset());
+ DCHECK_EQ(start, adjusted_item.TextShapeResult()->StartIndexForResult());
+ DCHECK_EQ(end, adjusted_item.EndOffset());
+ DCHECK_EQ(end, adjusted_item.TextShapeResult()->EndIndexForResult());
+ DCHECK_EQ(item->IsEmptyItem(), adjusted_item.IsEmptyItem());
+
+ items_->push_back(adjusted_item);
+ is_empty_inline_ &= adjusted_item.IsEmptyItem();
+ }
+ return true;
+}
+
+template <>
+bool NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::Append(
+ const String&,
+ LayoutObject*,
+ const Vector<NGInlineItem*>&) {
+ NOTREACHED();
+ return false;
+}
+
template <typename OffsetMappingBuilder>
void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::Append(
const String& string,
@@ -371,7 +452,7 @@ void NGInlineItemsBuilderTemplate<
if (text_.length() > start_offset) {
AppendItem(items_, NGInlineItem::kText, start_offset, text_.length(), style,
- layout_object);
+ layout_object, end_collapse != NGInlineItem::kNotCollapsible);
NGInlineItem& item = items_->back();
item.SetEndCollapseType(end_collapse);
is_empty_inline_ &= item.IsEmptyItem();
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
index f041c6b9f63..53e34b6cef3 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
@@ -19,7 +19,6 @@ namespace blink {
class ComputedStyle;
class LayoutObject;
class LayoutText;
-class NGInlineItem;
// NGInlineItemsBuilder builds a string and a list of NGInlineItem from inlines.
//
@@ -54,6 +53,12 @@ class CORE_TEMPLATE_CLASS_EXPORT NGInlineItemsBuilderTemplate {
// <span></span> or <span><float></float></span>.
bool IsEmptyInline() const { return is_empty_inline_; }
+ // Append existing items from an unchanged LayoutObject.
+ // Returns whether the existing items could be reused.
+ // NOTE: The state of the builder remains unchanged if the append operation
+ // fails (i.e. if it returns false).
+ bool Append(const String&, LayoutObject*, const Vector<NGInlineItem*>&);
+
// Append a string.
// When appending, spaces are collapsed according to CSS Text, The white space
// processing rules
@@ -149,6 +154,15 @@ class CORE_TEMPLATE_CLASS_EXPORT NGInlineItemsBuilderTemplate {
void Exit(LayoutObject*);
};
+template <>
+String NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::ToString();
+
+template <>
+bool NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::Append(
+ const String&,
+ LayoutObject*,
+ const Vector<NGInlineItem*>&);
+
extern template class CORE_EXTERN_TEMPLATE_EXPORT
NGInlineItemsBuilderTemplate<EmptyOffsetMappingBuilder>;
extern template class CORE_EXTERN_TEMPLATE_EXPORT
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
index d3b4b9eff09..ae5a273962c 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
@@ -8,6 +8,7 @@
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
namespace blink {
@@ -18,13 +19,6 @@ namespace {
EXPECT_EQ(start, (item).StartOffset()); \
EXPECT_EQ(end, (item).EndOffset());
-static scoped_refptr<ComputedStyle> CreateWhitespaceStyle(
- EWhiteSpace whitespace) {
- scoped_refptr<ComputedStyle> style(ComputedStyle::Create());
- style->SetWhiteSpace(whitespace);
- return style;
-}
-
static String GetCollapsed(const NGOffsetMappingBuilder& builder) {
Vector<unsigned> mapping = builder.DumpOffsetMappingForTesting();
@@ -47,40 +41,60 @@ static String GetCollapsed(const NGOffsetMappingBuilder& builder) {
return result.ToString();
}
-class NGInlineItemsBuilderTest : public testing::Test {
+class NGInlineItemsBuilderTest : public PageTestBase {
protected:
- void SetUp() override { style_ = ComputedStyle::Create(); }
+ void SetUp() override {
+ PageTestBase::SetUp();
+ style_ = ComputedStyle::Create();
+ }
void SetWhiteSpace(EWhiteSpace whitespace) {
style_->SetWhiteSpace(whitespace);
}
- const String& TestAppend(const String inputs[], int size) {
+ scoped_refptr<ComputedStyle> GetStyle(EWhiteSpace whitespace) {
+ if (whitespace == EWhiteSpace::kNormal)
+ return style_;
+ scoped_refptr<ComputedStyle> style(ComputedStyle::Create());
+ style->SetWhiteSpace(whitespace);
+ return style;
+ }
+
+ struct Input {
+ const String text;
+ EWhiteSpace whitespace = EWhiteSpace::kNormal;
+ LayoutText* layout_text = nullptr;
+ };
+
+ const String& TestAppend(Vector<Input> inputs) {
items_.clear();
NGInlineItemsBuilderForOffsetMapping builder(&items_);
- for (int i = 0; i < size; i++)
- builder.Append(inputs[i], style_.get());
+ for (Input& input : inputs) {
+ if (!input.layout_text)
+ input.layout_text = LayoutText::CreateEmptyAnonymous(GetDocument());
+ builder.Append(input.text, GetStyle(input.whitespace).get(),
+ input.layout_text);
+ }
text_ = builder.ToString();
collapsed_ = GetCollapsed(builder.GetOffsetMappingBuilder());
ValidateItems();
+ CheckReuseItemsProducesSameResult(inputs);
return text_;
}
const String& TestAppend(const String& input) {
- String inputs[] = {input};
- return TestAppend(inputs, 1);
+ return TestAppend({Input{input}});
+ }
+ const String& TestAppend(const Input& input1, const Input& input2) {
+ return TestAppend({input1, input2});
}
-
const String& TestAppend(const String& input1, const String& input2) {
- String inputs[] = {input1, input2};
- return TestAppend(inputs, 2);
+ return TestAppend(Input{input1}, Input{input2});
}
-
const String& TestAppend(const String& input1,
const String& input2,
const String& input3) {
- String inputs[] = {input1, input2, input3};
- return TestAppend(inputs, 3);
+ return TestAppend({{input1}, {input2}, {input3}});
}
void ValidateItems() {
@@ -94,6 +108,35 @@ class NGInlineItemsBuilderTest : public testing::Test {
EXPECT_EQ(current_offset, text_.length());
}
+ void CheckReuseItemsProducesSameResult(Vector<Input> inputs) {
+ Vector<NGInlineItem> reuse_items;
+ NGInlineItemsBuilder reuse_builder(&reuse_items);
+ for (Input& input : inputs) {
+ // Collect items for this LayoutObject.
+ DCHECK(input.layout_text);
+ Vector<NGInlineItem*> previous_items;
+ for (auto& item : items_) {
+ if (item.GetLayoutObject() == input.layout_text)
+ previous_items.push_back(&item);
+ }
+
+ // Try to re-use previous items, or Append if it was not re-usable.
+ bool reused = !previous_items.IsEmpty() &&
+ reuse_builder.Append(text_, nullptr, previous_items);
+ if (!reused)
+ reuse_builder.Append(input.text, style_.get());
+ }
+
+ // Currently, NGInlineItemsBuilder does not strip trailing spaces while
+ // NGInlineItemsBuilderForOffsetMapping does. See
+ // NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::ToString().
+ String reuse_text = reuse_builder.ToString();
+ if (!reuse_text.IsEmpty() && reuse_text != text_ &&
+ reuse_text[reuse_text.length() - 1] == kSpaceCharacter)
+ reuse_text = reuse_text.Substring(0, reuse_text.length() - 1);
+ EXPECT_EQ(text_, reuse_text);
+ }
+
Vector<NGInlineItem> items_;
String text_;
String collapsed_;
@@ -254,15 +297,11 @@ TEST_F(NGInlineItemsBuilderTest, CollapseBeforeAndAfterNewline) {
TEST_F(NGInlineItemsBuilderTest,
CollapsibleSpaceAfterNonCollapsibleSpaceAcrossElements) {
- NGInlineItemsBuilderForOffsetMapping builder(&items_);
- scoped_refptr<ComputedStyle> pre_wrap(
- CreateWhitespaceStyle(EWhiteSpace::kPreWrap));
- builder.Append("text ", pre_wrap.get());
- builder.Append(" text", style_.get());
- EXPECT_EQ("text text", builder.ToString())
+ EXPECT_EQ("text text",
+ TestAppend({"text ", EWhiteSpace::kPreWrap}, {" text"}))
<< "The whitespace in constructions like '<span style=\"white-space: "
"pre-wrap\">text <span><span> text</span>' does not collapse.";
- EXPECT_EQ("{}", GetCollapsed(builder.GetOffsetMappingBuilder()));
+ EXPECT_EQ("{}", collapsed_);
}
TEST_F(NGInlineItemsBuilderTest, CollapseZeroWidthSpaces) {
@@ -419,16 +458,11 @@ INSTANTIATE_TEST_CASE_P(NGInlineItemsBuilderTest,
TEST_P(CollapsibleSpaceTest, CollapsedSpaceAfterNoWrap) {
UChar space = GetParam();
- Vector<NGInlineItem> items;
- NGInlineItemsBuilderForOffsetMapping builder(&items);
- scoped_refptr<ComputedStyle> nowrap_style(ComputedStyle::Create());
- nowrap_style->SetWhiteSpace(EWhiteSpace::kNowrap);
- builder.Append(String("nowrap") + space, nowrap_style.get());
- builder.Append(" wrap", style_.get());
- EXPECT_EQ(String("nowrap "
- u"\u200B"
- "wrap"),
- builder.ToString());
+ EXPECT_EQ(
+ String("nowrap "
+ u"\u200B"
+ "wrap"),
+ TestAppend({String("nowrap") + space, EWhiteSpace::kNowrap}, {" wrap"}));
}
TEST_F(NGInlineItemsBuilderTest, BidiBlockOverride) {
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 5b4a1d99f34..0d04530a405 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
@@ -4,30 +4,27 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h"
-#include <algorithm>
-#include <limits>
#include <memory>
-#include <utility>
#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"
#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_item_result.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment.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_breaker.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h"
#include "third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
#include "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
#include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
@@ -42,8 +39,12 @@ struct NGLineAlign {
NGLineAlign(const NGLineInfo&);
NGLineAlign() = delete;
+ // The space to align or justify. This includes trailing spaces if exists.
LayoutUnit space;
+
+ // The end offset with trailing spaces excluded.
unsigned end_offset;
+ LayoutUnit trailing_spaces_width;
};
NGLineAlign::NGLineAlign(const NGLineInfo& line_info) {
@@ -55,14 +56,16 @@ NGLineAlign::NGLineAlign(const NGLineInfo& line_info) {
const NGInlineItemResult& item_result = *it;
if (!item_result.has_only_trailing_spaces) {
end_offset = item_result.end_offset;
+ space += trailing_spaces_width;
return;
}
- space += item_result.inline_size;
+ trailing_spaces_width += item_result.inline_size;
}
// An empty line, or only trailing spaces.
DCHECK_EQ(space, line_info.AvailableWidth() - line_info.TextIndent());
end_offset = line_info.StartOffset();
+ space += trailing_spaces_width;
}
} // namespace
@@ -80,15 +83,16 @@ NGInlineLayoutAlgorithm::NGInlineLayoutAlgorithm(
// lays out in visual order.
TextDirection::kLtr,
break_token),
+ baseline_type_(container_builder_.Style().GetFontBaseline()),
is_horizontal_writing_mode_(
blink::IsHorizontalWritingMode(space.GetWritingMode())) {
quirks_mode_ = inline_node.InLineHeightQuirksMode();
- unpositioned_floats_ = ConstraintSpace().UnpositionedFloats();
-
- if (!is_horizontal_writing_mode_)
- baseline_type_ = FontBaseline::kIdeographicBaseline;
}
+// Define the destructor here, so that we can forward-declare more in the
+// header.
+NGInlineLayoutAlgorithm::~NGInlineLayoutAlgorithm() = default;
+
NGInlineBoxState* NGInlineLayoutAlgorithm::HandleOpenTag(
const NGInlineItem& item,
const NGInlineItemResult& item_result) {
@@ -127,7 +131,7 @@ void NGInlineLayoutAlgorithm::PrepareBoxStates(
DCHECK(break_token->UseFirstLineStyle());
// Compute which tags are not closed at the beginning of this line.
- const Vector<NGInlineItem>& items = Node().Items();
+ const Vector<NGInlineItem>& items = line_info.ItemsData().items;
Vector<const NGInlineItem*, 16> open_items;
for (unsigned i = 0; i < break_token->ItemIndex(); i++) {
const NGInlineItem& item = items[i];
@@ -191,12 +195,13 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info,
baseline_type_);
}
- text_builder.SetItem(NGPhysicalTextFragment::kNormalText, &item_result,
+ text_builder.SetItem(NGPhysicalTextFragment::kNormalText,
+ line_info->ItemsData(), &item_result,
box->text_height);
line_box_.AddChild(text_builder.ToTextFragment(), box->text_top,
item_result.inline_size, item.BidiLevel());
} else if (item.Type() == NGInlineItem::kControl) {
- PlaceControlItem(item, &item_result, box);
+ PlaceControlItem(item, *line_info, &item_result, box);
} else if (item.Type() == NGInlineItem::kOpenTag) {
box = HandleOpenTag(item, item_result);
} else if (item.Type() == NGInlineItem::kCloseTag) {
@@ -244,6 +249,19 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info,
BidiReorder();
box_states_->UpdateAfterReorder(&line_box_);
LayoutUnit inline_size = box_states_->ComputeInlinePositions(&line_box_);
+
+ // Truncate the line if 'text-overflow: ellipsis' is set.
+ if (UNLIKELY(inline_size > line_info->AvailableWidth() &&
+ node_.GetLayoutBlockFlow()->ShouldTruncateOverflowingText())) {
+ inline_size = NGLineTruncator(node_, *line_info)
+ .TruncateLine(inline_size, &line_box_);
+ }
+
+ // Create box fragmetns if needed. After this point forward, |line_box_| is a
+ // tree structure.
+ if (box_states_->HasBoxFragments())
+ box_states_->CreateBoxFragments(&line_box_);
+
const NGLineHeightMetrics& line_box_metrics =
box_states_->LineBoxState().metrics;
@@ -280,6 +298,8 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info,
if (IsLtr(line_info->BaseDirection()))
line_bfc_offset.line_offset += line_info->TextIndent();
+ if (line_info->UseFirstLineStyle())
+ container_builder_.SetStyleVariant(NGStyleVariant::kFirstLine);
container_builder_.AddChildren(line_box_);
container_builder_.SetInlineSize(inline_size);
container_builder_.SetBaseDirection(line_info->BaseDirection());
@@ -288,12 +308,13 @@ void NGInlineLayoutAlgorithm::CreateLine(NGLineInfo* line_info,
}
void NGInlineLayoutAlgorithm::PlaceControlItem(const NGInlineItem& item,
+ const NGLineInfo& line_info,
NGInlineItemResult* item_result,
NGInlineBoxState* box) {
DCHECK_EQ(item.Type(), NGInlineItem::kControl);
DCHECK_EQ(item.Length(), 1u);
DCHECK(!item.TextShapeResult());
- UChar character = Node().Text()[item.StartOffset()];
+ UChar character = line_info.ItemsData().text_content[item.StartOffset()];
NGPhysicalTextFragment::NGTextType type;
switch (character) {
case kNewlineCharacter:
@@ -321,7 +342,8 @@ void NGInlineLayoutAlgorithm::PlaceControlItem(const NGInlineItem& item,
NGTextFragmentBuilder text_builder(Node(),
ConstraintSpace().GetWritingMode());
- text_builder.SetItem(type, item_result, box->text_height);
+ text_builder.SetItem(type, line_info.ItemsData(), item_result,
+ box->text_height);
line_box_.AddChild(text_builder.ToTextFragment(), box->text_top,
item_result->inline_size, item.BidiLevel());
}
@@ -450,7 +472,9 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(NGLineInfo* line_info) {
// Construct the line text to compute spacing for.
String line_text =
- Node().Text(line_info->StartOffset(), align.end_offset).ToString();
+ StringView(line_info->ItemsData().text_content, line_info->StartOffset(),
+ align.end_offset - line_info->StartOffset())
+ .ToString();
// Append a hyphen if the last word is hyphenated. The hyphen is in
// |ShapeResult|, but not in text. |ShapeResultSpacing| needs the text that
@@ -498,8 +522,9 @@ LayoutUnit NGInlineLayoutAlgorithm::OffsetForTextAlign(
// Justification is applied in earlier phase, see PlaceItems().
DCHECK_NE(text_align, ETextAlign::kJustify);
+ NGLineAlign align(line_info);
return LineOffsetForTextAlign(text_align, line_info.BaseDirection(),
- NGLineAlign(line_info).space);
+ align.space, align.trailing_spaces_width);
}
LayoutUnit NGInlineLayoutAlgorithm::ComputeContentSize(
@@ -538,10 +563,20 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
bool is_empty_inline = Node().IsEmptyInline();
- if (!is_empty_inline) {
- DCHECK(ConstraintSpace().UnpositionedFloats().IsEmpty());
+ if (is_empty_inline) {
+ // We're just going to collapse through this one, so whatever went in on one
+ // side will go out on the other side. The position of the adjoining floats
+ // will be affected by any subsequent block, until the BFC offset is
+ // resolved.
+ container_builder_.AddAdjoiningFloatTypes(
+ ConstraintSpace().AdjoiningFloatTypes());
+ } else {
DCHECK(ConstraintSpace().MarginStrut().IsEmpty());
container_builder_.SetBfcOffset(ConstraintSpace().BfcOffset());
+
+ // The BFC offset was determined before entering this algorithm. This means
+ // that there should be no adjoining floats.
+ DCHECK(!ConstraintSpace().AdjoiningFloatTypes());
}
// In order to get the correct list of layout opportunities, we need to
@@ -552,10 +587,9 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
// If we are an empty inline, we don't have to run the full algorithm, we can
// return now as we should have positioned all of our floats.
if (is_empty_inline) {
- DCHECK_EQ(handled_item_index, Node().Items().size());
+ DCHECK_EQ(handled_item_index, Node().ItemsData(false).items.size());
container_builder_.SwapPositionedFloats(&positioned_floats_);
- container_builder_.SwapUnpositionedFloats(&unpositioned_floats_);
container_builder_.SetEndMarginStrut(ConstraintSpace().MarginStrut());
container_builder_.SetExclusionSpace(std::move(initial_exclusion_space));
@@ -572,7 +606,7 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
// We query all the layout opportunities on the initial exclusion space up
// front, as if the line breaker may add floats and change the opportunities.
- Vector<NGLayoutOpportunity> opportunities =
+ const Vector<NGLayoutOpportunity> opportunities =
initial_exclusion_space->AllLayoutOpportunities(
ConstraintSpace().BfcOffset(),
ConstraintSpace().AvailableSize().inline_size);
@@ -583,7 +617,30 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
std::unique_ptr<NGExclusionSpace> exclusion_space;
NGInlineBreakToken* break_token = BreakToken();
- for (const auto& opportunity : opportunities) {
+ LayoutUnit line_block_size;
+ LayoutUnit block_delta;
+ const auto* opportunities_it = opportunities.begin();
+ while (opportunities_it != opportunities.end()) {
+ const NGLayoutOpportunity& opportunity = *opportunities_it;
+
+#if DCHECK_IS_ON()
+ // Make sure the last opportunity has the correct properties.
+ if (opportunities_it + 1 == opportunities.end()) {
+ // We shouldn't have any shapes affecting the last opportunity.
+ DCHECK(!opportunity.HasShapeExclusions());
+ DCHECK_EQ(line_block_size, LayoutUnit());
+ DCHECK_EQ(block_delta, LayoutUnit());
+
+ // The opportunity should match the given available size, (however need
+ // to check if the inline-size got saturated first).
+ if (opportunity.rect.InlineSize() != LayoutUnit::Max()) {
+ DCHECK_EQ(opportunity.rect.InlineSize(),
+ ConstraintSpace().AvailableSize().inline_size);
+ }
+ DCHECK_EQ(opportunity.rect.BlockSize(), LayoutUnit::Max());
+ }
+#endif
+
// Reset any state that may have been modified in a previous pass.
positioned_floats.clear();
unpositioned_floats_.clear();
@@ -591,24 +648,44 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
exclusion_space =
std::make_unique<NGExclusionSpace>(*initial_exclusion_space);
+ NGLineLayoutOpportunity line_opportunity =
+ opportunity.ComputeLineLayoutOpportunity(ConstraintSpace(),
+ line_block_size, block_delta);
+
NGLineInfo line_info;
- NGLineBreaker line_breaker(Node(), NGLineBreakerMode::kContent,
- constraint_space_, &positioned_floats,
- &unpositioned_floats_, exclusion_space.get(),
- handled_item_index, break_token);
+ NGLineBreaker line_breaker(
+ Node(), NGLineBreakerMode::kContent, constraint_space_,
+ &positioned_floats, &unpositioned_floats_, &container_builder_,
+ exclusion_space.get(), handled_item_index, break_token);
// TODO(ikilpatrick): Does this always succeed when we aren't an empty
// inline?
- if (!line_breaker.NextLine(opportunity, &line_info))
+ if (!line_breaker.NextLine(line_opportunity, &line_info))
break;
// If this fragment will be larger than the inline-size of the opportunity,
- // *and* the opportunity is smaller than the available inline-size,
- // continue to the next opportunity.
- if (line_info.Width() > opportunity.rect.InlineSize() &&
- opportunity.rect.InlineSize() !=
- ConstraintSpace().AvailableSize().inline_size)
+ // *and* the opportunity is smaller than the available inline-size, and the
+ // container autowraps, continue to the next opportunity.
+ if (line_info.Width() > line_opportunity.AvailableInlineSize() &&
+ ConstraintSpace().AvailableSize().inline_size !=
+ line_opportunity.AvailableFloatInlineSize() &&
+ Node().Style().AutoWrap()) {
+ // Shapes are *special*. We need to potentially increment the block-delta
+ // by 1px each loop to properly test each potential position of the line.
+ if (UNLIKELY(opportunity.HasShapeExclusions()) &&
+ block_delta < opportunity.rect.BlockSize() &&
+ !opportunity.IsBlockDeltaBelowShapes(block_delta)) {
+ block_delta += LayoutUnit(1);
+ line_block_size = LayoutUnit();
+ } else {
+ // We've either don't have any shapes, or run out of block-delta space
+ // to test, proceed to the next layout opportunity.
+ block_delta = LayoutUnit();
+ line_block_size = LayoutUnit();
+ ++opportunities_it;
+ }
continue;
+ }
PrepareBoxStates(line_info, break_token);
CreateLine(&line_info, exclusion_space.get());
@@ -616,8 +693,33 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
// We now can check the block-size of the fragment, and it fits within the
// opportunity.
LayoutUnit block_size = container_builder_.ComputeBlockSize();
- if (block_size > opportunity.rect.BlockSize())
+
+ // Now that we have the block-size of the line, we can re-test the layout
+ // opportunity to see if we fit into the (potentially) non-rectangular
+ // shape area.
+ // If the AvailableInlineSize changes we need to run the line breaker again
+ // with the calculated line_block_size. This is *safe* as the line breaker
+ // won't produce a line which has a larger block-size, (as it can only
+ // decrease or stay the same size).
+ if (UNLIKELY(opportunity.HasShapeExclusions())) {
+ NGLineLayoutOpportunity line_opportunity_with_height =
+ opportunity.ComputeLineLayoutOpportunity(ConstraintSpace(),
+ block_size, block_delta);
+
+ if (line_opportunity_with_height.AvailableInlineSize() !=
+ line_opportunity.AvailableInlineSize()) {
+ line_block_size = block_size;
+ continue;
+ }
+ }
+
+ // Check if the line will fit in the current opportunity.
+ if (block_size + block_delta > opportunity.rect.BlockSize()) {
+ block_delta = LayoutUnit();
+ line_block_size = LayoutUnit();
+ ++opportunities_it;
continue;
+ }
if (opportunity.rect.BlockStartOffset() >
ConstraintSpace().BfcOffset().block_offset)
@@ -662,7 +764,7 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
// TODO(ikilpatrick): Do we need to always add the OOFs here?
unsigned NGInlineLayoutAlgorithm::PositionLeadingItems(
NGExclusionSpace* exclusion_space) {
- const Vector<NGInlineItem>& items = Node().Items();
+ const Vector<NGInlineItem>& items = Node().ItemsData(false).items;
bool is_empty_inline = Node().IsEmptyInline();
LayoutUnit bfc_line_offset = ConstraintSpace().BfcOffset().line_offset;
@@ -675,10 +777,12 @@ unsigned NGInlineLayoutAlgorithm::PositionLeadingItems(
NGBoxStrut margins =
ComputeMarginsForContainer(ConstraintSpace(), node.Style());
- unpositioned_floats_.push_back(NGUnpositionedFloat::Create(
+ auto unpositioned_float = NGUnpositionedFloat::Create(
ConstraintSpace().AvailableSize(),
ConstraintSpace().PercentageResolutionSize(), bfc_line_offset,
- bfc_line_offset, margins, node, /* break_token */ nullptr));
+ bfc_line_offset, margins, node, /* break_token */ nullptr);
+ AddUnpositionedFloat(&unpositioned_floats_, &container_builder_,
+ std::move(unpositioned_float));
} else if (is_empty_inline &&
item.Type() == NGInlineItem::kOutOfFlowPositioned) {
NGBlockNode node(ToLayoutBox(item.GetLayoutObject()));
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 b0c9a2edb91..017544f453a 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
@@ -6,9 +6,6 @@
#define NGInlineLayoutAlgorithm_h
#include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
@@ -23,7 +20,10 @@ class NGConstraintSpace;
class NGInlineBreakToken;
class NGInlineNode;
class NGInlineItem;
-class NGLineBoxFragmentBuilder;
+class NGInlineLayoutStateStack;
+class NGLineInfo;
+struct NGInlineBoxState;
+struct NGInlineItemResult;
struct NGPositionedFloat;
// A class for laying out an inline formatting context, i.e. a block with inline
@@ -40,6 +40,7 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final
NGInlineLayoutAlgorithm(NGInlineNode,
const NGConstraintSpace&,
NGInlineBreakToken* = nullptr);
+ ~NGInlineLayoutAlgorithm() override;
void CreateLine(NGLineInfo*, NGExclusionSpace*);
@@ -59,6 +60,7 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final
void BidiReorder();
void PlaceControlItem(const NGInlineItem&,
+ const NGLineInfo&,
NGInlineItemResult*,
NGInlineBoxState*);
void PlaceGeneratedContent(scoped_refptr<NGPhysicalFragment>,
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 ed5f88b97c1..654b7eb887e 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
@@ -5,14 +5,12 @@
#include "third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h"
#include "third_party/blink/renderer/core/dom/tag_collection.h"
-#include "third_party/blink/renderer/core/layout/line/inline_text_box.h"
+#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
-#include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
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 92aabbded97..bf394eba54f 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
@@ -5,39 +5,28 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include <algorithm>
-#include <limits>
#include <memory>
-#include <utility>
+#include "third_party/blink/renderer/core/layout/layout_block_flow.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/layout_text_fragment.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment.h"
-#include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h"
#include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h"
-#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h"
-#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
namespace blink {
@@ -49,6 +38,11 @@ template <typename OffsetMappingBuilder>
void ClearNeedsLayoutIfUpdatingLayout(LayoutObject* node) {
node->ClearNeedsLayout();
node->ClearNeedsCollectInlines();
+ // Reset previous items if they cannot be reused to prevent stale items
+ // for subsequent layouts. Items that can be reused have already been
+ // added to the builder.
+ if (node->IsLayoutNGText())
+ ToLayoutNGText(node)->ClearInlineItems();
}
template <>
@@ -67,19 +61,33 @@ void ClearNeedsLayoutIfUpdatingLayout<NGOffsetMappingBuilder>(LayoutObject*) {}
template <typename OffsetMappingBuilder>
void CollectInlinesInternal(
LayoutBlockFlow* block,
- NGInlineItemsBuilderTemplate<OffsetMappingBuilder>* builder) {
+ NGInlineItemsBuilderTemplate<OffsetMappingBuilder>* builder,
+ String* previous_text) {
builder->EnterBlock(block->Style());
LayoutObject* node = GetLayoutObjectForFirstChildNode(block);
while (node) {
if (node->IsText()) {
LayoutText* layout_text = ToLayoutText(node);
- if (UNLIKELY(layout_text->IsWordBreak())) {
- builder->AppendBreakOpportunity(node->Style(), layout_text);
- } else {
- builder->Append(layout_text->GetText(), node->Style(), layout_text);
+
+ // If the LayoutText element hasn't changed, reuse the existing items.
+
+ // if the last ended with space and this starts with space, do not allow
+ // reuse. builder->MightCollapseWithPreceding(*previous_text)
+ bool item_reused = false;
+ if (node->IsLayoutNGText() && ToLayoutNGText(node)->HasValidLayout() &&
+ previous_text) {
+ item_reused = builder->Append(*previous_text, ToLayoutNGText(node),
+ ToLayoutNGText(node)->InlineItems());
}
- ClearNeedsLayoutIfUpdatingLayout<OffsetMappingBuilder>(layout_text);
+ // If not create a new item as needed.
+ if (!item_reused) {
+ if (UNLIKELY(layout_text->IsWordBreak()))
+ builder->AppendBreakOpportunity(node->Style(), layout_text);
+ else
+ builder->Append(layout_text->GetText(), node->Style(), layout_text);
+ }
+ ClearNeedsLayoutIfUpdatingLayout<OffsetMappingBuilder>(layout_text);
} else if (node->IsFloating()) {
// Add floats and positioned objects in the same way as atomic inlines.
@@ -145,6 +153,33 @@ void CollectInlinesInternal(
builder->ExitBlock();
}
+static bool NeedsShaping(const NGInlineItem& item) {
+ return item.Type() == NGInlineItem::kText && !item.TextShapeResult();
+}
+
+// Determine if reshape is needed for ::first-line style.
+bool FirstLineNeedsReshape(const ComputedStyle& first_line_style,
+ const ComputedStyle& base_style) {
+ const Font& base_font = base_style.GetFont();
+ const Font& first_line_font = first_line_style.GetFont();
+ return &base_font != &first_line_font && base_font != first_line_font;
+}
+
+// Make a string to the specified length, either by truncating if longer, or
+// appending space characters if shorter.
+void TruncateOrPadText(String* text, unsigned length) {
+ if (text->length() > length) {
+ *text = text->Substring(0, length);
+ } else if (text->length() < length) {
+ StringBuilder builder;
+ builder.ReserveCapacity(length);
+ builder.Append(*text);
+ while (builder.length() < length)
+ builder.Append(kSpaceCharacter);
+ *text = builder.ToString();
+ }
+}
+
} // namespace
NGInlineNode::NGInlineNode(LayoutBlockFlow* block)
@@ -159,13 +194,29 @@ bool NGInlineNode::InLineHeightQuirksMode() const {
return GetDocument().InLineHeightQuirksMode();
}
+bool NGInlineNode::CanContainFirstFormattedLine() const {
+ // TODO(kojii): In LayoutNG, leading OOF creates an anonymous block box,
+ // and that |LayoutBlockFlow::CanContainFirstFormattedLine()| does not work.
+ // crbug.com/734554
+ LayoutObject* layout_object = GetLayoutBlockFlow();
+ if (!layout_object->IsAnonymousBlock())
+ return true;
+ for (;;) {
+ layout_object = layout_object->PreviousSibling();
+ if (!layout_object)
+ return true;
+ if (!layout_object->IsFloatingOrOutOfFlowPositioned())
+ return false;
+ }
+}
+
NGInlineNodeData* NGInlineNode::MutableData() {
return ToLayoutBlockFlow(box_)->GetNGInlineNodeData();
}
bool NGInlineNode::IsPrepareLayoutFinished() const {
const NGInlineNodeData* data = ToLayoutBlockFlow(box_)->GetNGInlineNodeData();
- return data && !data->text_content_.IsNull();
+ return data && !data->text_content.IsNull();
}
const NGInlineNodeData& NGInlineNode::Data() const {
@@ -174,28 +225,19 @@ const NGInlineNodeData& NGInlineNode::Data() const {
return *ToLayoutBlockFlow(box_)->GetNGInlineNodeData();
}
-const Vector<NGInlineItem>& NGInlineNode::Items(bool is_first_line) const {
- const NGInlineNodeData& data = Data();
- if (!is_first_line || !data.first_line_items_)
- return data.items_;
- return *data.first_line_items_;
-}
-
-NGInlineItemRange NGInlineNode::Items(unsigned start, unsigned end) {
- return NGInlineItemRange(&MutableData()->items_, start, end);
-}
-
-void NGInlineNode::InvalidatePrepareLayout() {
+void NGInlineNode::InvalidatePrepareLayoutForTest() {
GetLayoutBlockFlow()->ResetNGInlineNodeData();
DCHECK(!IsPrepareLayoutFinished());
}
void NGInlineNode::PrepareLayoutIfNeeded() {
+ std::unique_ptr<NGInlineNodeData> previous_data;
LayoutBlockFlow* block_flow = GetLayoutBlockFlow();
if (IsPrepareLayoutFinished()) {
if (!block_flow->NeedsCollectInlines())
return;
+ previous_data.reset(block_flow->TakeNGInlineNodeData());
block_flow->ResetNGInlineNodeData();
}
@@ -203,9 +245,11 @@ void NGInlineNode::PrepareLayoutIfNeeded() {
// NGInlineNode represent a collection of adjacent non-atomic inlines.
NGInlineNodeData* data = MutableData();
DCHECK(data);
- CollectInlines(data);
+ CollectInlines(data, previous_data.get());
SegmentText(data);
- ShapeText(data);
+ ShapeText(data, previous_data.get());
+ ShapeTextForFirstLineIfNeeded(data);
+ AssociateItemsWithInlines(data);
DCHECK_EQ(data, MutableData());
block_flow->ClearNeedsCollectInlines();
@@ -213,10 +257,10 @@ void NGInlineNode::PrepareLayoutIfNeeded() {
#if DCHECK_IS_ON()
// ComputeOffsetMappingIfNeeded() runs some integrity checks as part of
// creating offset mapping. Run the check, and discard the result.
- DCHECK(!data->offset_mapping_);
+ DCHECK(!data->offset_mapping);
ComputeOffsetMappingIfNeeded();
- DCHECK(data->offset_mapping_);
- data->offset_mapping_.reset();
+ DCHECK(data->offset_mapping);
+ data->offset_mapping.reset();
#endif
}
@@ -228,45 +272,56 @@ const NGInlineNodeData& NGInlineNode::EnsureData() {
const NGOffsetMapping* NGInlineNode::ComputeOffsetMappingIfNeeded() {
DCHECK(!GetLayoutBlockFlow()->GetDocument().NeedsLayoutTreeUpdate());
- if (!Data().offset_mapping_) {
+ NGInlineNodeData* data = MutableData();
+ if (!data->offset_mapping) {
// TODO(xiaochengh): ComputeOffsetMappingIfNeeded() discards the
// NGInlineItems and text content built by |builder|, because they are
// already there in NGInlineNodeData. For efficiency, we should make
// |builder| not construct items and text content.
Vector<NGInlineItem> items;
NGInlineItemsBuilderForOffsetMapping builder(&items);
- CollectInlinesInternal(GetLayoutBlockFlow(), &builder);
- builder.ToString();
+ CollectInlinesInternal(GetLayoutBlockFlow(), &builder, nullptr);
+ String text = builder.ToString();
+
+ // The trailing space of the text for offset mapping may be removed. If not,
+ // share the string instance.
+ if (text == data->text_content)
+ text = data->text_content;
// TODO(xiaochengh): This doesn't compute offset mapping correctly when
// text-transform CSS property changes text length.
NGOffsetMappingBuilder& mapping_builder = builder.GetOffsetMappingBuilder();
- mapping_builder.SetDestinationString(Text());
- MutableData()->offset_mapping_ =
+ mapping_builder.SetDestinationString(text);
+ data->offset_mapping =
std::make_unique<NGOffsetMapping>(mapping_builder.Build());
}
- return Data().offset_mapping_.get();
+ return data->offset_mapping.get();
}
// Depth-first-scan of all LayoutInline and LayoutText nodes that make up this
// NGInlineNode object. Collects LayoutText items, merging them up into the
// parent LayoutInline where possible, and joining all text content in a single
// string to allow bidi resolution and shaping of the entire block.
-void NGInlineNode::CollectInlines(NGInlineNodeData* data) {
- DCHECK(data->text_content_.IsNull());
- DCHECK(data->items_.IsEmpty());
+void NGInlineNode::CollectInlines(NGInlineNodeData* data,
+ NGInlineNodeData* previous_data) {
+ DCHECK(data->text_content.IsNull());
+ DCHECK(data->items.IsEmpty());
LayoutBlockFlow* block = GetLayoutBlockFlow();
block->WillCollectInlines();
- NGInlineItemsBuilder builder(&data->items_);
- CollectInlinesInternal(block, &builder);
- data->text_content_ = builder.ToString();
+
+ String* previous_text =
+ previous_data ? &previous_data->text_content : nullptr;
+ NGInlineItemsBuilder builder(&data->items);
+ CollectInlinesInternal(block, &builder, previous_text);
+ data->text_content = builder.ToString();
+
// Set |is_bidi_enabled_| for all UTF-16 strings for now, because at this
// point the string may or may not contain RTL characters.
// |SegmentText()| will analyze the text and reset |is_bidi_enabled_| if it
// doesn't contain any RTL characters.
data->is_bidi_enabled_ =
- !data->text_content_.Is8Bit() || builder.HasBidiControls();
+ !data->text_content.Is8Bit() || builder.HasBidiControls();
data->is_empty_inline_ = builder.IsEmptyInline();
}
@@ -277,8 +332,8 @@ void NGInlineNode::SegmentText(NGInlineNodeData* data) {
}
NGBidiParagraph bidi;
- data->text_content_.Ensure16Bit();
- if (!bidi.SetParagraph(data->text_content_, Style())) {
+ data->text_content.Ensure16Bit();
+ if (!bidi.SetParagraph(data->text_content, Style())) {
// On failure, give up bidi resolving and reordering.
data->is_bidi_enabled_ = false;
data->SetBaseDirection(TextDirection::kLtr);
@@ -293,9 +348,9 @@ void NGInlineNode::SegmentText(NGInlineNodeData* data) {
return;
}
- Vector<NGInlineItem>& items = data->items_;
+ Vector<NGInlineItem>& items = data->items;
unsigned item_index = 0;
- for (unsigned start = 0; start < data->text_content_.length();) {
+ for (unsigned start = 0; start < data->text_content.length();) {
UBiDiLevel level;
unsigned end = bidi.GetLogicalRun(start, &level);
DCHECK_EQ(items[item_index].start_offset_, start);
@@ -313,19 +368,21 @@ void NGInlineNode::SegmentText(NGInlineNodeData* data) {
#endif
}
-void NGInlineNode::ShapeText(NGInlineNodeData* data) {
+void NGInlineNode::ShapeText(NGInlineItemsData* data,
+ NGInlineItemsData* previous_data) {
// TODO(eae): Add support for shaping latin-1 text?
- data->text_content_.Ensure16Bit();
- ShapeText(data->text_content_, &data->items_);
-
- ShapeTextForFirstLineIfNeeded(data);
+ data->text_content.Ensure16Bit();
+ ShapeText(data->text_content, &data->items,
+ previous_data ? &previous_data->text_content : nullptr);
}
void NGInlineNode::ShapeText(const String& text_content,
- Vector<NGInlineItem>* items) {
- // Shape each item with the full context of the entire node.
+ Vector<NGInlineItem>* items,
+ const String* previous_text) {
+ // Provide full context of the entire node to the shaper.
HarfBuzzShaper shaper(text_content.Characters16(), text_content.length());
ShapeResultSpacing<String> spacing(text_content);
+
for (unsigned index = 0; index < items->size();) {
NGInlineItem& start_item = (*items)[index];
if (start_item.Type() != NGInlineItem::kText) {
@@ -339,6 +396,12 @@ void NGInlineNode::ShapeText(const String& text_content,
unsigned end_offset = start_item.EndOffset();
for (; end_index < items->size(); end_index++) {
const NGInlineItem& item = (*items)[end_index];
+
+ if (item.Type() == NGInlineItem::kControl) {
+ // Do not shape across control characters (line breaks, zero width
+ // spaces, etc).
+ break;
+ }
if (item.Type() == NGInlineItem::kText) {
// Shape adjacent items together if the font and direction matches to
// allow ligatures and kerning to apply.
@@ -359,6 +422,43 @@ void NGInlineNode::ShapeText(const String& text_content,
}
}
+ // Shaping a single item. Skip if the existing results remain valid.
+ if (previous_text && end_offset == start_item.EndOffset() &&
+ !NeedsShaping(start_item)) {
+ DCHECK_EQ(start_item.StartOffset(),
+ start_item.TextShapeResult()->StartIndexForResult());
+ DCHECK_EQ(start_item.EndOffset(),
+ start_item.TextShapeResult()->EndIndexForResult());
+ index++;
+ continue;
+ }
+
+ // 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;
+ }
+ }
+
+ // 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 =
shaper.Shape(&font, direction, start_item.StartOffset(), end_offset);
if (UNLIKELY(spacing.SetSpacing(font.GetFontDescription())))
@@ -404,9 +504,28 @@ void NGInlineNode::ShapeTextForFirstLineIfNeeded(NGInlineNodeData* data) {
if (block_style == first_line_style)
return;
- auto first_line_items = std::make_unique<Vector<NGInlineItem>>();
- first_line_items->AppendVector(data->items_);
- for (auto& item : *first_line_items) {
+ auto first_line_items = std::make_unique<NGInlineItemsData>();
+ first_line_items->text_content = data->text_content;
+ bool needs_reshape = false;
+ if (first_line_style->TextTransform() != block_style->TextTransform()) {
+ // TODO(kojii): This logic assumes that text-transform is applied only to
+ // ::first-line, and does not work when the base style has text-transform
+ // and ::first-line has different text-transform.
+ first_line_style->ApplyTextTransform(&first_line_items->text_content);
+ if (first_line_items->text_content != data->text_content) {
+ // TODO(kojii): When text-transform changes the length, we need to adjust
+ // offset in NGInlineItem, or re-collect inlines. Other classes such as
+ // line breaker need to support the scenario too. For now, we force the
+ // string to be the same length to prevent them from crashing. This may
+ // result in a missing or a duplicate character if the length changes.
+ TruncateOrPadText(&first_line_items->text_content,
+ data->text_content.length());
+ needs_reshape = true;
+ }
+ }
+
+ first_line_items->items.AppendVector(data->items);
+ for (auto& item : first_line_items->items) {
if (item.style_) {
DCHECK(item.layout_object_);
item.style_ = item.layout_object_->FirstLineStyle();
@@ -415,15 +534,26 @@ void NGInlineNode::ShapeTextForFirstLineIfNeeded(NGInlineNodeData* data) {
}
// Re-shape if the font is different.
- const Font& font = block_style->GetFont();
- const Font& first_line_font = first_line_style->GetFont();
- if (&font != &first_line_font && font != first_line_font) {
- ShapeText(data->text_content_, first_line_items.get());
- }
+ if (needs_reshape || FirstLineNeedsReshape(*first_line_style, *block_style))
+ ShapeText(first_line_items.get());
data->first_line_items_ = std::move(first_line_items);
}
+void NGInlineNode::AssociateItemsWithInlines(NGInlineNodeData* data) {
+ LayoutObject* last_object = nullptr;
+ for (auto& item : data->items) {
+ LayoutObject* object = item.GetLayoutObject();
+ if (object && object->IsLayoutNGText()) {
+ LayoutNGText* layout_text = ToLayoutNGText(object);
+ if (object != last_object)
+ layout_text->ClearInlineItems();
+ layout_text->AddInlineItem(&item);
+ }
+ last_object = object;
+ }
+}
+
scoped_refptr<NGLayoutResult> NGInlineNode::Layout(
const NGConstraintSpace& constraint_space,
NGBreakToken* break_token) {
@@ -446,6 +576,7 @@ static LayoutUnit ComputeContentSize(NGInlineNode node,
NGConstraintSpaceBuilder(writing_mode, node.InitialContainingBlockSize())
.SetTextDirection(style.Direction())
.SetAvailableSize({available_inline_size, NGSizeIndefinite})
+ .SetIsIntermediateLayout(true)
.ToConstraintSpace(writing_mode);
Vector<NGPositionedFloat> positioned_floats;
@@ -454,8 +585,7 @@ static LayoutUnit ComputeContentSize(NGInlineNode node,
scoped_refptr<NGInlineBreakToken> break_token;
NGLineInfo line_info;
NGExclusionSpace empty_exclusion_space;
- NGLayoutOpportunity opportunity(NGBfcRect(
- NGBfcOffset(), NGBfcOffset(available_inline_size, LayoutUnit::Max())));
+ NGLineLayoutOpportunity line_opportunity(available_inline_size);
LayoutUnit result;
LayoutUnit previous_floats_inline_size =
input.float_left_inline_size + input.float_right_inline_size;
@@ -463,9 +593,10 @@ static LayoutUnit ComputeContentSize(NGInlineNode node,
unpositioned_floats.clear();
NGLineBreaker line_breaker(node, mode, *space, &positioned_floats,
- &unpositioned_floats, &empty_exclusion_space, 0u,
- break_token.get());
- if (!line_breaker.NextLine(opportunity, &line_info))
+ &unpositioned_floats,
+ nullptr /* container_builder */,
+ &empty_exclusion_space, 0u, break_token.get());
+ if (!line_breaker.NextLine(line_opportunity, &line_info))
break;
break_token = line_breaker.CreateBreakToken(line_info, nullptr);
@@ -490,14 +621,9 @@ static LayoutUnit ComputeContentSize(NGInlineNode node,
NGBlockNode float_node = unpositioned_float->node;
const ComputedStyle& float_style = float_node.Style();
- Optional<MinMaxSize> child_minmax;
- if (NeedMinMaxSizeForContentContribution(float_style)) {
- MinMaxSizeInput zero_input; // Floats don't intrude into floats.
- child_minmax = float_node.ComputeMinMaxSize(zero_input);
- }
-
- MinMaxSize child_sizes =
- ComputeMinAndMaxContentContribution(float_style, child_minmax);
+ MinMaxSizeInput zero_input; // Floats don't intrude into floats.
+ MinMaxSize child_sizes = ComputeMinAndMaxContentContribution(
+ writing_mode, float_node, zero_input);
LayoutUnit child_inline_margins =
ComputeMinMaxMargins(style, float_node).InlineSum();
@@ -560,7 +686,7 @@ MinMaxSize NGInlineNode::ComputeMinMaxSize(const MinMaxSizeInput& input) {
void NGInlineNode::CheckConsistency() const {
#if DCHECK_IS_ON()
- const Vector<NGInlineItem>& items = Data().items_;
+ const Vector<NGInlineItem>& items = Data().items;
for (const NGInlineItem& item : items) {
DCHECK(!item.GetLayoutObject() || !item.Style() ||
item.Style() == item.GetLayoutObject()->Style());
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
index 333c5b1b092..7685706584d 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
@@ -7,28 +7,19 @@
#include "third_party/blink/renderer/core/core_export.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/layout/ng/ng_layout_input_node.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
-template <typename OffsetMappingBuilder>
-class NGInlineItemsBuilderTemplate;
-
-class EmptyOffsetMappingBuilder;
-class LayoutBlockFlow;
-struct MinMaxSize;
class NGConstraintSpace;
class NGInlineItem;
-class NGInlineItemRange;
-using NGInlineItemsBuilder =
- NGInlineItemsBuilderTemplate<EmptyOffsetMappingBuilder>;
-struct NGInlineNodeData;
class NGLayoutResult;
class NGOffsetMapping;
class NGInlineNodeLegacy;
+struct MinMaxSize;
+struct NGInlineItemsData;
// Represents an anonymous block box to be laid out, that contains consecutive
// inline nodes and their descendants.
@@ -55,17 +46,12 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
MinMaxSize ComputeMinMaxSize(const MinMaxSizeInput&);
// Instruct to re-compute |PrepareLayout| on the next layout.
- void InvalidatePrepareLayout();
+ void InvalidatePrepareLayoutForTest();
- const String& Text() const { return Data().text_content_; }
- StringView Text(unsigned start_offset, unsigned end_offset) const {
- return StringView(Data().text_content_, start_offset,
- end_offset - start_offset);
+ const NGInlineItemsData& ItemsData(bool is_first_line) const {
+ return Data().ItemsData(is_first_line);
}
- const Vector<NGInlineItem>& Items(bool is_first_line = false) const;
- NGInlineItemRange Items(unsigned start_index, unsigned end_index);
-
// 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.
@@ -76,8 +62,10 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
bool IsEmptyInline() { return EnsureData().is_empty_inline_; }
- void AssertOffset(unsigned index, unsigned offset) const;
- void AssertEndOffset(unsigned index, unsigned offset) const;
+ // @return if this node can contain the "first formatted line".
+ // https://www.w3.org/TR/CSS22/selector.html#first-formatted-line
+ bool CanContainFirstFormattedLine() const;
+
void CheckConsistency() const;
String ToString() const;
@@ -89,11 +77,16 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
// calling the Layout method.
void PrepareLayoutIfNeeded();
- void CollectInlines(NGInlineNodeData*);
+ void CollectInlines(NGInlineNodeData*,
+ NGInlineNodeData* previous_data = nullptr);
void SegmentText(NGInlineNodeData*);
- void ShapeText(NGInlineNodeData*);
- void ShapeText(const String&, Vector<NGInlineItem>*);
+ void ShapeText(NGInlineItemsData*,
+ NGInlineItemsData* previous_data = nullptr);
+ void ShapeText(const String& text,
+ Vector<NGInlineItem>*,
+ const String* previous_text);
void ShapeTextForFirstLineIfNeeded(NGInlineNodeData*);
+ void AssociateItemsWithInlines(NGInlineNodeData*);
NGInlineNodeData* MutableData();
const NGInlineNodeData& Data() const;
@@ -103,15 +96,6 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
friend class NGInlineNodeLegacy;
};
-inline void NGInlineNode::AssertOffset(unsigned index, unsigned offset) const {
- Data().items_[index].AssertOffset(offset);
-}
-
-inline void NGInlineNode::AssertEndOffset(unsigned index,
- unsigned offset) const {
- Data().items_[index].AssertEndOffset(offset);
-}
-
DEFINE_TYPE_CASTS(NGInlineNode,
NGLayoutInputNode,
node,
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.cc
index a45f608f7b5..07eaa48884b 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.cc
@@ -4,13 +4,6 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h"
-#include "third_party/blink/renderer/core/dom/node.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
-#include "third_party/blink/renderer/core/style/computed_style.h"
-
namespace blink {
-NGInlineNodeData::NGInlineNodeData() = default;
-NGInlineNodeData::~NGInlineNodeData() = default;
-
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
index 2d8efffd2d5..dbf328bc9ea 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
@@ -11,16 +11,14 @@
namespace blink {
-class NGOffsetMapping;
-
// Data which is required for inline nodes.
-struct CORE_EXPORT NGInlineNodeData {
- // The constructor and destructor can't be implicit or inlined, because they
- // need full definition of NGOffsetMapping.
- NGInlineNodeData();
- ~NGInlineNodeData();
-
+struct CORE_EXPORT NGInlineNodeData : NGInlineItemsData {
private:
+ const NGInlineItemsData& ItemsData(bool is_first_line) const {
+ return !is_first_line || !first_line_items_
+ ? (const NGInlineItemsData&)*this
+ : *first_line_items_;
+ }
TextDirection BaseDirection() const {
return static_cast<TextDirection>(base_direction_);
}
@@ -33,18 +31,12 @@ struct CORE_EXPORT NGInlineNodeData {
friend class NGInlineNodeForTest;
friend class NGOffsetMappingTest;
- // Text content for all inline items represented by a single NGInlineNode.
- // Encoded either as UTF-16 or latin-1 depending on the content.
- String text_content_;
- Vector<NGInlineItem> items_;
-
- // |items_| to use for the first line, when the node has :first-line rules.
- // Items have different ComputedStyle, and may also have different ShapeResult
- // if fonts are different.
- std::unique_ptr<Vector<NGInlineItem>> first_line_items_;
-
- // The DOM to text content offset mapping of this inline node.
- std::unique_ptr<NGOffsetMapping> offset_mapping_;
+ // Items to use for the first line, when the node has :first-line rules.
+ //
+ // Items have different ComputedStyle, and may also have different
+ // text_content and ShapeResult if 'text-transform' is applied or fonts are
+ // different.
+ std::unique_ptr<NGInlineItemsData> first_line_items_;
unsigned is_bidi_enabled_ : 1;
unsigned base_direction_ : 1; // TextDirection
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 ca628714647..f04e5e70b56 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
@@ -22,29 +22,29 @@ class NGInlineNodeForTest : public NGInlineNode {
public:
using NGInlineNode::NGInlineNode;
- std::string Text() const { return Data().text_content_.Utf8().data(); }
- Vector<NGInlineItem>& Items() { return MutableData()->items_; }
+ std::string Text() const { return Data().text_content.Utf8().data(); }
+ Vector<NGInlineItem>& Items() { return MutableData()->items; }
static Vector<NGInlineItem>& Items(NGInlineNodeData& data) {
- return data.items_;
+ return data.items;
}
void Append(const String& text,
const ComputedStyle* style = nullptr,
LayoutObject* layout_object = nullptr) {
NGInlineNodeData* data = MutableData();
- unsigned start = data->text_content_.length();
- data->text_content_.append(text);
- data->items_.push_back(NGInlineItem(NGInlineItem::kText, start,
- start + text.length(), style,
- layout_object));
+ unsigned start = data->text_content.length();
+ data->text_content.append(text);
+ data->items.push_back(NGInlineItem(NGInlineItem::kText, start,
+ start + text.length(), style,
+ layout_object));
data->is_empty_inline_ = false;
}
void Append(UChar character) {
NGInlineNodeData* data = MutableData();
- data->text_content_.append(character);
- unsigned end = data->text_content_.length();
- data->items_.push_back(
+ data->text_content.append(character);
+ unsigned end = data->text_content.length();
+ data->items.push_back(
NGInlineItem(NGInlineItem::kBidiControl, end - 1, end, nullptr));
data->is_bidi_enabled_ = true;
data->is_empty_inline_ = false;
@@ -52,8 +52,8 @@ class NGInlineNodeForTest : public NGInlineNode {
void ClearText() {
NGInlineNodeData* data = MutableData();
- data->text_content_ = String();
- data->items_.clear();
+ data->text_content = String();
+ data->items.clear();
data->is_empty_inline_ = true;
}
@@ -93,7 +93,7 @@ class NGInlineNodeTest : public NGLayoutTest {
if (!layout_block_flow_)
SetupHtml("t", "<div id=t style='font:10px'>test</div>");
NGInlineNodeForTest node(layout_block_flow_);
- node.InvalidatePrepareLayout();
+ node.InvalidatePrepareLayoutForTest();
return node;
}
@@ -304,7 +304,6 @@ TEST_F(NGInlineNodeTest, SegmentSplit1To2) {
NGInlineNodeForTest node = CreateInlineNode();
node.Append(u"Hello \u05E2\u05D1\u05E8\u05D9\u05EA");
node.SegmentText();
- ASSERT_EQ(2u, node.Items().size());
Vector<NGInlineItem>& items = node.Items();
ASSERT_EQ(2u, items.size());
TEST_ITEM_OFFSET_DIR(items[0], 0u, 6u, TextDirection::kLtr);
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 eeef40c579c..2b7a7e3d58e 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
@@ -5,7 +5,6 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h"
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
@@ -56,6 +55,25 @@ const NGPhysicalFragment* NGLineBoxFragmentBuilder::Child::PhysicalFragment()
: fragment.get();
}
+NGLineBoxFragmentBuilder::Child*
+NGLineBoxFragmentBuilder::ChildList::FirstInFlowChild() {
+ for (auto& child : *this) {
+ if (child.HasInFlowFragment())
+ return &child;
+ }
+ return nullptr;
+}
+
+NGLineBoxFragmentBuilder::Child*
+NGLineBoxFragmentBuilder::ChildList::LastInFlowChild() {
+ for (auto it = rbegin(); it != rend(); it++) {
+ auto& child = *it;
+ if (child.HasInFlowFragment())
+ return &child;
+ }
+ return nullptr;
+}
+
void NGLineBoxFragmentBuilder::ChildList::InsertChild(
unsigned index,
scoped_refptr<NGLayoutResult> layout_result,
@@ -128,28 +146,32 @@ scoped_refptr<NGLayoutResult> NGLineBoxFragmentBuilder::ToLineBoxFragment() {
NGPhysicalSize physical_size = Size().ConvertToPhysical(writing_mode);
NGPhysicalOffsetRect contents_visual_rect({}, physical_size);
+ NGPhysicalOffsetRect scrollable_overflow({}, physical_size);
for (size_t i = 0; i < children_.size(); ++i) {
NGPhysicalFragment* child = children_[i].get();
child->SetOffset(offsets_[i].ConvertToPhysical(
writing_mode, Direction(), physical_size, child->Size()));
child->PropagateContentsVisualRect(&contents_visual_rect);
+ NGPhysicalOffsetRect child_scroll_overflow = child->ScrollableOverflow();
+ child_scroll_overflow.offset += child->Offset();
+ scrollable_overflow.Unite(child_scroll_overflow);
}
scoped_refptr<NGPhysicalLineBoxFragment> fragment =
base::AdoptRef(new NGPhysicalLineBoxFragment(
- Style(), physical_size, children_, contents_visual_rect, metrics_,
- base_direction_,
+ Style(), style_variant_, physical_size, children_,
+ contents_visual_rect, scrollable_overflow, metrics_, base_direction_,
break_token_ ? std::move(break_token_)
: NGInlineBreakToken::Create(node_)));
return base::AdoptRef(new NGLayoutResult(
std::move(fragment), oof_positioned_descendants_, positioned_floats_,
- unpositioned_floats_, unpositioned_list_marker_,
- std::move(exclusion_space_), bfc_offset_, end_margin_strut_,
+ unpositioned_list_marker_, std::move(exclusion_space_), bfc_offset_,
+ end_margin_strut_,
/* intrinsic_block_size */ LayoutUnit(),
/* minimal_space_shortage */ LayoutUnit::Max(), EBreakBetween::kAuto,
EBreakBetween::kAuto, /* has_forced_break */ false, is_pushed_by_floats_,
- NGLayoutResult::kSuccess));
+ adjoining_floats_, NGLayoutResult::kSuccess));
}
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h
index 9f4ed515a70..9ea29183fb1 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
@@ -9,15 +9,15 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h"
#include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/platform/wtf/allocator.h"
namespace blink {
class ComputedStyle;
class NGInlineBreakToken;
-class NGInlineNode;
class NGPhysicalFragment;
+struct NGPositionedFloat;
class CORE_EXPORT NGLineBoxFragmentBuilder final
: public NGContainerFragmentBuilder {
@@ -78,6 +78,14 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
bidi_level(bidi_level) {}
// Create an in-flow |NGPhysicalFragment|.
Child(scoped_refptr<NGPhysicalFragment> fragment,
+ NGLogicalOffset offset,
+ LayoutUnit inline_size,
+ UBiDiLevel bidi_level)
+ : fragment(std::move(fragment)),
+ offset(offset),
+ inline_size(inline_size),
+ bidi_level(bidi_level) {}
+ Child(scoped_refptr<NGPhysicalFragment> fragment,
LayoutUnit block_offset,
LayoutUnit inline_size,
UBiDiLevel bidi_level)
@@ -130,6 +138,15 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
using const_iterator = Vector<Child, 16>::const_iterator;
const_iterator begin() const { return children_.begin(); }
const_iterator end() const { return children_.end(); }
+ using reverse_iterator = Vector<Child, 16>::reverse_iterator;
+ reverse_iterator rbegin() { return children_.rbegin(); }
+ reverse_iterator rend() { return children_.rend(); }
+ using const_reverse_iterator = Vector<Child, 16>::const_reverse_iterator;
+ const_reverse_iterator rbegin() const { return children_.rbegin(); }
+ const_reverse_iterator rend() const { return children_.rend(); }
+
+ Child* FirstInFlowChild();
+ Child* LastInFlowChild();
// Add a child. Accepts all constructor arguments for |Child|.
template <class... Args>
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 343a5fec56c..79294415634 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
@@ -8,12 +8,12 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h"
@@ -35,24 +35,40 @@ inline bool CanBreakAfterLast(const NGInlineItemResults& item_results) {
} // namespace
+NGLineBreaker::LineData::LineData(NGInlineNode node,
+ const NGInlineBreakToken* break_token) {
+ is_first_formatted_line = (!break_token || (!break_token->ItemIndex() &&
+ !break_token->TextOffset())) &&
+ node.CanContainFirstFormattedLine();
+ use_first_line_style = is_first_formatted_line && node.GetLayoutObject()
+ ->GetDocument()
+ .GetStyleEngine()
+ .UsesFirstLineRules();
+}
+
NGLineBreaker::NGLineBreaker(
NGInlineNode node,
NGLineBreakerMode mode,
const NGConstraintSpace& space,
Vector<NGPositionedFloat>* positioned_floats,
Vector<scoped_refptr<NGUnpositionedFloat>>* unpositioned_floats,
+ NGContainerFragmentBuilder* container_builder,
NGExclusionSpace* exclusion_space,
unsigned handled_float_index,
const NGInlineBreakToken* break_token)
- : node_(node),
+ : line_(node, break_token),
+ node_(node),
+ items_data_(node.ItemsData(line_.use_first_line_style)),
mode_(mode),
constraint_space_(space),
positioned_floats_(positioned_floats),
unpositioned_floats_(unpositioned_floats),
+ container_builder_(container_builder),
exclusion_space_(exclusion_space),
- break_iterator_(node.Text()),
- shaper_(node.Text().Characters16(), node.Text().length()),
- spacing_(node.Text()),
+ break_iterator_(items_data_.text_content),
+ shaper_(items_data_.text_content.Characters16(),
+ items_data_.text_content.length()),
+ spacing_(items_data_.text_content),
handled_floats_end_item_index_(handled_float_index),
base_direction_(node_.BaseDirection()),
in_line_height_quirks_mode_(node.InLineHeightQuirksMode()) {
@@ -63,11 +79,15 @@ NGLineBreaker::NGLineBreaker(
item_index_ = break_token->ItemIndex();
offset_ = break_token->TextOffset();
previous_line_had_forced_break_ = break_token->IsForcedBreak();
- node.AssertOffset(item_index_, offset_);
+ items_data_.AssertOffset(item_index_, offset_);
ignore_floats_ = break_token->IgnoreFloats();
}
}
+// Define the destructor here, so that we can forward-declare more in the
+// header.
+NGLineBreaker::~NGLineBreaker() = default;
+
inline NGInlineItemResult* NGLineBreaker::AddItem(
const NGInlineItem& item,
unsigned end_offset,
@@ -106,59 +126,53 @@ inline void NGLineBreaker::ComputeCanBreakAfter(
auto_wrap_ && break_iterator_.IsBreakable(item_result->end_offset);
}
-// @return if this is the "first formatted line".
-// https://www.w3.org/TR/CSS22/selector.html#first-formatted-line
-bool NGLineBreaker::IsFirstFormattedLine() const {
- if (item_index_ || offset_)
- return false;
-
- // TODO(kojii): In LayoutNG, leading OOF creates an anonymous block box,
- // and that |CanContainFirstFormattedLine()| does not work.
- // crbug.com/734554
- // return node_.GetLayoutBlockFlow()->CanContainFirstFormattedLine();
- LayoutObject* layout_object = node_.GetLayoutBlockFlow();
- if (!layout_object->IsAnonymousBlock())
- return true;
- for (;;) {
- layout_object = layout_object->PreviousSibling();
- if (!layout_object)
- return true;
- if (!layout_object->IsFloatingOrOutOfFlowPositioned())
+// True if |item| is trailing; i.e., |item| and all items after it are opaque to
+// whitespace collapsing.
+bool NGLineBreaker::IsTrailing(const NGInlineItem& item,
+ const NGLineInfo& line_info) const {
+ const Vector<NGInlineItem>& items = line_info.ItemsData().items;
+ for (const NGInlineItem* it = &item; it != items.end(); ++it) {
+ if (it->EndCollapseType() != NGInlineItem::kOpaqueToCollapsing)
return false;
}
+ return true;
}
// Compute the base direction for bidi algorithm for this line.
-void NGLineBreaker::ComputeBaseDirection() {
+void NGLineBreaker::ComputeBaseDirection(const NGLineInfo& line_info) {
// If 'unicode-bidi' is not 'plaintext', use the base direction of the block.
if (!previous_line_had_forced_break_ ||
node_.Style().GetUnicodeBidi() != UnicodeBidi::kPlaintext)
return;
// If 'unicode-bidi: plaintext', compute the base direction for each paragraph
// (separated by forced break.)
- const String& text = node_.Text();
+ const String& text = line_info.ItemsData().text_content;
if (text.Is8Bit())
return;
size_t end_offset = text.find(kNewlineCharacter, offset_);
- base_direction_ = NGBidiParagraph::BaseDirectionForString(node_.Text(
- offset_, end_offset == kNotFound ? text.length() : end_offset));
+ base_direction_ = NGBidiParagraph::BaseDirectionForString(
+ end_offset == kNotFound
+ ? StringView(text, offset_)
+ : StringView(text, offset_, end_offset - offset_));
}
// Initialize internal states for the next line.
-void NGLineBreaker::PrepareNextLine(const NGLayoutOpportunity& opportunity,
- NGLineInfo* line_info) {
+void NGLineBreaker::PrepareNextLine(
+ const NGLineLayoutOpportunity& line_opportunity,
+ NGLineInfo* line_info) {
NGInlineItemResults* item_results = &line_info->Results();
item_results->clear();
line_info->SetStartOffset(offset_);
- line_info->SetLineStyle(node_, constraint_space_, IsFirstFormattedLine(),
- previous_line_had_forced_break_);
+ line_info->SetLineStyle(
+ node_, items_data_, constraint_space_, line_.is_first_formatted_line,
+ line_.use_first_line_style, previous_line_had_forced_break_);
// Set the initial style of this line from the break token. Example:
// <p>...<span>....</span></p>
// When the line wraps in <span>, the 2nd line needs to start with the style
// of the <span>.
override_break_anywhere_ = false;
SetCurrentStyle(current_style_ ? *current_style_ : line_info->LineStyle());
- ComputeBaseDirection();
+ ComputeBaseDirection(*line_info);
line_info->SetBaseDirection(base_direction_);
line_.is_after_forced_break = false;
@@ -168,17 +182,12 @@ void NGLineBreaker::PrepareNextLine(const NGLayoutOpportunity& opportunity,
// regardless of 'text-indent'.
line_.position = line_info->TextIndent();
- line_.opportunity = opportunity;
- line_.line_left_bfc_offset = opportunity.rect.LineStartOffset();
- line_.line_right_bfc_offset = opportunity.rect.LineEndOffset();
- bfc_block_offset_ = opportunity.rect.BlockStartOffset();
+ line_.line_opportunity = line_opportunity;
}
-bool NGLineBreaker::NextLine(const NGLayoutOpportunity& opportunity,
+bool NGLineBreaker::NextLine(const NGLineLayoutOpportunity& line_opportunity,
NGLineInfo* line_info) {
- bfc_block_offset_ = constraint_space_.BfcOffset().block_offset;
-
- PrepareNextLine(opportunity, line_info);
+ PrepareNextLine(line_opportunity, line_info);
BreakLine(line_info);
if (line_info->Results().IsEmpty())
@@ -186,22 +195,15 @@ bool NGLineBreaker::NextLine(const NGLayoutOpportunity& opportunity,
// TODO(kojii): There are cases where we need to PlaceItems() without creating
// line boxes. These cases need to be reviewed.
- if (line_.should_create_line_box) {
- if (!line_.CanFit() &&
- node_.GetLayoutBlockFlow()->ShouldTruncateOverflowingText()) {
- TruncateOverflowingText(line_info);
- }
-
+ if (line_.should_create_line_box)
ComputeLineLocation(line_info);
- }
return true;
}
void NGLineBreaker::BreakLine(NGLineInfo* line_info) {
NGInlineItemResults* item_results = &line_info->Results();
- const Vector<NGInlineItem>& items =
- node_.Items(line_info->UseFirstLineStyle());
+ const Vector<NGInlineItem>& items = line_info->ItemsData().items;
LineBreakState state = LineBreakState::kContinue;
while (state != LineBreakState::kDone) {
// Check overflow even if |item_index_| is at the end of the block, because
@@ -214,6 +216,7 @@ void NGLineBreaker::BreakLine(NGLineInfo* line_info) {
// If we reach at the end of the block, this is the last line.
DCHECK_LE(item_index_, items.size());
if (item_index_ == items.size()) {
+ RemoveTrailingCollapsibleSpace(line_info);
line_info->SetIsLastLine(true);
return;
}
@@ -255,7 +258,7 @@ void NGLineBreaker::BreakLine(NGLineInfo* line_info) {
} else if (item.Type() == NGInlineItem::kOpenTag) {
HandleOpenTag(item, AddItem(item, item_results));
} else if (item.Type() == NGInlineItem::kFloating) {
- HandleFloat(item, AddItem(item, item_results));
+ HandleFloat(item, line_info, AddItem(item, item_results));
} else if (item.Type() == NGInlineItem::kOutOfFlowPositioned) {
DCHECK_EQ(item.Length(), 0u);
AddItem(item, item_results);
@@ -291,14 +294,14 @@ void NGLineBreaker::UpdatePosition(const NGInlineItemResults& results) {
}
void NGLineBreaker::ComputeLineLocation(NGLineInfo* line_info) const {
- LayoutUnit bfc_line_offset = line_.line_left_bfc_offset;
+ LayoutUnit bfc_line_offset = line_.line_opportunity.line_left_offset;
LayoutUnit available_width = line_.AvailableWidth();
// Negative margins can make the position negative, but the inline size is
// always positive or 0.
- line_info->SetLineBfcOffset({bfc_line_offset, bfc_block_offset_},
- available_width,
- line_.position.ClampNegativeToZero());
+ line_info->SetLineBfcOffset(
+ {bfc_line_offset, line_.line_opportunity.bfc_block_offset},
+ available_width, line_.position.ClampNegativeToZero());
}
NGLineBreaker::LineBreakState NGLineBreaker::HandleText(
@@ -394,7 +397,7 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result,
// * If offset == item.EndOffset(): the break opportunity at the end fits,
// or the first break opportunity is beyond the end.
// There may be room for more characters.
- // * If width > available_width: The first break opporunity does not fit.
+ // * If width > available_width: The first break opportunity does not fit.
// offset is the first break opportunity, either inside, at the end, or
// beyond the end.
if (item_result->end_offset < item.EndOffset()) {
@@ -438,6 +441,7 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleTrailingSpaces(
NGInlineItemResult* item_result = AddItem(item, end, item_results);
item_result->has_only_trailing_spaces = true;
+ // TODO(kojii): Should reshape if it's not safe to break.
item_result->shape_result = item.TextShapeResult()->SubRange(offset_, end);
item_result->inline_size = item_result->shape_result->SnappedWidth();
line_.position += item_result->inline_size;
@@ -455,6 +459,44 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleTrailingSpaces(
return LineBreakState::kTrailing;
}
+// Remove trailing collapsible spaces in |line_info|.
+// https://drafts.csswg.org/css-text-3/#white-space-phase-2
+void NGLineBreaker::RemoveTrailingCollapsibleSpace(NGLineInfo* line_info) {
+ NGInlineItemResults* item_results = &line_info->Results();
+ if (item_results->IsEmpty())
+ return;
+ for (auto it = item_results->rbegin(); it != item_results->rend(); ++it) {
+ NGInlineItemResult& item_result = *it;
+ DCHECK(item_result.item);
+ const NGInlineItem& item = *item_result.item;
+ if (item.EndCollapseType() == NGInlineItem::kOpaqueToCollapsing)
+ continue;
+ if (item.Type() != NGInlineItem::kText)
+ return;
+ const String& text = Text();
+ if (text[item_result.end_offset - 1] != kSpaceCharacter)
+ return;
+ DCHECK(item.Style());
+ if (!item.Style()->CollapseWhiteSpace())
+ return;
+
+ // We have a trailing collapsible space. Remove it.
+ line_.position -= item_result.inline_size;
+ --item_result.end_offset;
+ if (item_result.end_offset == item_result.start_offset) {
+ unsigned index = std::distance(item_results->begin(), &item_result);
+ item_results->EraseAt(index);
+ } else {
+ // TODO(kojii): Should reshape if it's not safe to break.
+ item_result.shape_result = item_result.shape_result->SubRange(
+ item_result.start_offset, item_result.end_offset);
+ item_result.inline_size = item_result.shape_result->SnappedWidth();
+ line_.position += item_result.inline_size;
+ }
+ return;
+ }
+}
+
void NGLineBreaker::AppendHyphen(const NGInlineItem& item,
NGLineInfo* line_info) {
DCHECK(item.Style());
@@ -565,16 +607,33 @@ void NGLineBreaker::HandleAtomicInline(const NGInlineItem& item,
line_.should_create_line_box = true;
NGInlineItemResult* item_result = AddItem(item, &line_info->Results());
- item_result->layout_result =
- NGBlockNode(ToLayoutBox(item.GetLayoutObject()))
- .LayoutAtomicInline(constraint_space_,
- line_info->UseFirstLineStyle());
- DCHECK(item_result->layout_result->PhysicalFragment());
-
- item_result->inline_size =
- NGFragment(constraint_space_.GetWritingMode(),
- *item_result->layout_result->PhysicalFragment())
- .InlineSize();
+ // When we're just computing min/max content sizes, we can skip the full
+ // layout and just compute those sizes. On the other hand, for regular
+ // layout we need to do the full layout and get the layout result.
+ // Doing a full layout for min/max content can also have undesirable
+ // side effects when that falls back to legacy layout.
+ if (mode_ == NGLineBreakerMode::kContent) {
+ item_result->layout_result =
+ NGBlockNode(ToLayoutBox(item.GetLayoutObject()))
+ .LayoutAtomicInline(constraint_space_,
+ line_info->LineStyle().GetFontBaseline(),
+ line_info->UseFirstLineStyle());
+ DCHECK(item_result->layout_result->PhysicalFragment());
+
+ item_result->inline_size =
+ NGFragment(constraint_space_.GetWritingMode(),
+ *item_result->layout_result->PhysicalFragment())
+ .InlineSize();
+ } else {
+ NGBlockNode block_node(ToLayoutBox(item.GetLayoutObject()));
+ MinMaxSizeInput input;
+ MinMaxSize sizes = ComputeMinAndMaxContentContribution(
+ constraint_space_.GetWritingMode(), block_node, input,
+ &constraint_space_);
+ item_result->inline_size = mode_ == NGLineBreakerMode::kMinContent
+ ? sizes.min_size
+ : sizes.max_size;
+ }
DCHECK(item.Style());
item_result->margins =
@@ -601,9 +660,8 @@ void NGLineBreaker::HandleAtomicInline(const NGInlineItem& item,
// We have this check if there are already UnpositionedFloats as we aren't
// allowed to position a float "above" another float which has come before us
// in the document.
-//
-// TODO(glebl): Add the support of clearance for inline floats.
void NGLineBreaker::HandleFloat(const NGInlineItem& item,
+ NGLineInfo* line_info,
NGInlineItemResult* item_result) {
// When rewind occurs, an item may be handled multiple times.
// Since floats are put into a separate list, avoid handling same floats
@@ -619,6 +677,13 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
if (item_index_ <= handled_floats_end_item_index_ || ignore_floats_)
return;
+ // Floats need to know the current line width to determine whether to put it
+ // into the current line or to the next line. Remove trailing spaces if this
+ // float is trailing, because whitespace should be collapsed across floats,
+ // and this logic requires the width after trailing spaces are collapsed.
+ if (IsTrailing(item, *line_info))
+ RemoveTrailingCollapsibleSpace(line_info);
+
NGBlockNode node(ToLayoutBox(item.GetLayoutObject()));
const ComputedStyle& float_style = node.Style();
@@ -641,49 +706,47 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
margins.InlineSum())
.ClampNegativeToZero();
+ LayoutUnit bfc_block_offset = line_.line_opportunity.bfc_block_offset;
+
// The float should be positioned after the current line if:
- // - It can't fit.
+ // - It can't fit within the non-shape area. (Assuming the current position
+ // also is strictly within the non-shape area).
// - It will be moved down due to block-start edge alignment.
// - It will be moved down due to clearance.
// - We are currently computing our min/max-content size. (We use the
// unpositioned_floats to manually adjust the min/max-content size after
// the line breaker has run).
bool float_after_line =
- !line_.CanFit(inline_margin_size) ||
- exclusion_space_->LastFloatBlockStart() > bfc_block_offset_ ||
+ !line_.CanFloatFit(inline_margin_size) ||
+ exclusion_space_->LastFloatBlockStart() > bfc_block_offset ||
exclusion_space_->ClearanceOffset(float_style.Clear()) >
- bfc_block_offset_ ||
+ bfc_block_offset ||
mode_ != NGLineBreakerMode::kContent;
// Check if we already have a pending float. That's because a float cannot be
// higher than any block or floated box generated before.
if (!unpositioned_floats_->IsEmpty() || float_after_line) {
- unpositioned_floats_->push_back(std::move(unpositioned_float));
+ AddUnpositionedFloat(unpositioned_floats_, container_builder_,
+ std::move(unpositioned_float));
} else {
- LayoutUnit origin_block_offset = bfc_block_offset_;
-
NGPositionedFloat positioned_float = PositionFloat(
- origin_block_offset, constraint_space_.BfcOffset().block_offset,
+ bfc_block_offset, constraint_space_.BfcOffset().block_offset,
unpositioned_float.get(), constraint_space_, exclusion_space_);
positioned_floats_->push_back(positioned_float);
DCHECK_EQ(positioned_float.bfc_offset.block_offset,
- bfc_block_offset_ + margins.block_start);
+ bfc_block_offset + margins.block_start);
- if (float_style.Floating() == EFloat::kLeft) {
- line_.line_left_bfc_offset = std::max(
- line_.line_left_bfc_offset,
- positioned_float.bfc_offset.line_offset + inline_margin_size -
- margins.LineLeft(TextDirection::kLtr));
- } else {
- line_.line_right_bfc_offset =
- std::min(line_.line_right_bfc_offset,
- positioned_float.bfc_offset.line_offset -
- margins.LineLeft(TextDirection::kLtr));
- }
+ NGLayoutOpportunity opportunity = exclusion_space_->FindLayoutOpportunity(
+ {constraint_space_.BfcOffset().line_offset, bfc_block_offset},
+ constraint_space_.AvailableSize().inline_size, NGLogicalSize());
+
+ DCHECK_EQ(bfc_block_offset, opportunity.rect.BlockStartOffset());
+
+ line_.line_opportunity = opportunity.ComputeLineLayoutOpportunity(
+ constraint_space_, line_.line_opportunity.line_block_size,
+ LayoutUnit());
- DCHECK_GE(line_.line_left_bfc_offset, LayoutUnit());
- DCHECK_GE(line_.line_right_bfc_offset, LayoutUnit());
DCHECK_GE(line_.AvailableWidth(), LayoutUnit());
}
}
@@ -783,7 +846,7 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item,
// be a break opportunity after the space. The break_iterator cannot
// compute this because it considers break opportunities are before a run
// of spaces.
- const String& text = node_.Text();
+ const String& text = Text();
if (offset_ < text.length() && IsBreakableSpace(text[offset_])) {
item_result->can_break_after = true;
return;
@@ -858,7 +921,7 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleOverflow(
#endif
item_index_ = item_result->item_index;
offset_ = item_result->end_offset;
- node_.AssertOffset(item_index_, offset_);
+ items_data_.AssertOffset(item_index_, offset_);
} else {
Rewind(line_info, i + 1);
}
@@ -879,7 +942,7 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleOverflow(
}
// Let this line overflow.
- // If there was a break opporunity, the overflow should stop there.
+ // If there was a break opportunity, the overflow should stop there.
if (break_before) {
Rewind(line_info, break_before);
return LineBreakState::kTrailing;
@@ -918,8 +981,7 @@ void NGLineBreaker::Rewind(NGLineInfo* line_info, unsigned new_end) {
// paint invalidations, hit testing, etc.
LayoutObject* NGLineBreaker::CurrentLayoutObject(
const NGLineInfo& line_info) const {
- const Vector<NGInlineItem>& items =
- node_.Items(line_info.UseFirstLineStyle());
+ const Vector<NGInlineItem>& items = line_info.ItemsData().items;
DCHECK_LE(item_index_, items.size());
// Find the next item that has LayoutObject. Some items such as bidi controls
// do not have LayoutObject.
@@ -936,56 +998,6 @@ LayoutObject* NGLineBreaker::CurrentLayoutObject(
return nullptr;
}
-// Truncate overflowing text and append ellipsis.
-void NGLineBreaker::TruncateOverflowingText(NGLineInfo* line_info) {
- // The ellipsis is styled according to the line style.
- const Font& font = line_info->LineStyle().GetFont();
- const SimpleFontData* font_data = font.PrimaryFont();
- DCHECK(font_data);
- String ellipsis =
- font_data && font_data->GlyphForCharacter(kHorizontalEllipsisCharacter)
- ? String(&kHorizontalEllipsisCharacter, 1)
- : String(u"...");
- HarfBuzzShaper shaper(ellipsis.Characters16(), ellipsis.length());
- scoped_refptr<ShapeResult> shape_result =
- shaper.Shape(&font, line_info->BaseDirection());
-
- // Truncate the line to (available_width - ellipsis_width) using 'line-break:
- // anywhere'.
- unsigned saved_item_index = item_index_;
- unsigned saved_offset = offset_;
- override_break_anywhere_ = true;
- break_iterator_.SetBreakType(LineBreakType::kBreakCharacter);
- HandleOverflow(line_info,
- line_.AvailableWidth() - shape_result->SnappedWidth());
-
- // Find the LayoutObject this ellpsis is tied to.
- LayoutObject* layout_object = CurrentLayoutObject(*line_info);
-
- // Restore item_index/offset to before HandleOverflow().
- item_index_ = saved_item_index;
- offset_ = saved_offset;
-
- // Ellipsis should not have text decorations. Reset if it's set.
- scoped_refptr<const ComputedStyle> style = &line_info->LineStyle();
- if (style->TextDecorationsInEffect() != TextDecoration::kNone) {
- scoped_refptr<ComputedStyle> ellipsis_style =
- ComputedStyle::CreateAnonymousStyleWithDisplay(*style,
- EDisplay::kInline);
- ellipsis_style->ResetTextDecoration();
- ellipsis_style->ClearAppliedTextDecorations();
- style = std::move(ellipsis_style);
- }
-
- // The ellipsis should appear at the logical end of the line.
- // This is stored seprately from other results so that it can be appended
- // after bidi reorder.
- NGTextFragmentBuilder builder(node_, constraint_space_.GetWritingMode());
- builder.SetText(layout_object, ellipsis, style, true /* is_ellipsis_style */,
- std::move(shape_result));
- SetLineEndFragment(builder.ToTextFragment(), line_info);
-}
-
void NGLineBreaker::SetCurrentStyle(const ComputedStyle& style) {
current_style_ = &style;
@@ -1041,7 +1053,7 @@ void NGLineBreaker::MoveToNextOf(const NGInlineItemResult& item_result) {
scoped_refptr<NGInlineBreakToken> NGLineBreaker::CreateBreakToken(
const NGLineInfo& line_info,
std::unique_ptr<const NGInlineLayoutStateStack> state_stack) const {
- const Vector<NGInlineItem>& items = node_.Items();
+ const Vector<NGInlineItem>& items = Items();
if (item_index_ >= items.size())
return NGInlineBreakToken::Create(node_);
return NGInlineBreakToken::Create(
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 98e15a95b37..b0838c40a67 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
@@ -6,7 +6,7 @@
#define NGLineBreaker_h
#include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/layout/ng/exclusions/ng_layout_opportunity.h"
+#include "third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
@@ -17,10 +17,12 @@
namespace blink {
class Hyphenation;
+class NGContainerFragmentBuilder;
class NGInlineBreakToken;
class NGInlineItem;
class NGInlineLayoutStateStack;
struct NGPositionedFloat;
+struct NGUnpositionedFloat;
// The line breaker needs to know which mode its in to properly handle floats.
enum class NGLineBreakerMode { kContent, kMinContent, kMaxContent };
@@ -38,14 +40,15 @@ class CORE_EXPORT NGLineBreaker {
const NGConstraintSpace&,
Vector<NGPositionedFloat>*,
Vector<scoped_refptr<NGUnpositionedFloat>>*,
+ NGContainerFragmentBuilder* container_builder,
NGExclusionSpace*,
unsigned handled_float_index,
const NGInlineBreakToken* = nullptr);
- ~NGLineBreaker() = default;
+ ~NGLineBreaker();
// Compute the next line break point and produces NGInlineItemResults for
// the line.
- bool NextLine(const NGLayoutOpportunity&, NGLineInfo*);
+ bool NextLine(const NGLineLayoutOpportunity& line_opportunity, NGLineInfo*);
// Create an NGInlineBreakToken for the last line returned by NextLine().
scoped_refptr<NGInlineBreakToken> CreateBreakToken(
@@ -63,15 +66,19 @@ class CORE_EXPORT NGLineBreaker {
struct LineData {
STACK_ALLOCATED();
+ LineData(NGInlineNode node, const NGInlineBreakToken* break_token);
+
// The current position from inline_start. Unlike NGInlineLayoutAlgorithm
// that computes position in visual order, this position in logical order.
LayoutUnit position;
- // The current opportunity.
- NGLayoutOpportunity opportunity;
+ NGLineLayoutOpportunity line_opportunity;
+
+ // True if this line is the "first formatted line".
+ // https://www.w3.org/TR/CSS22/selector.html#first-formatted-line
+ bool is_first_formatted_line;
- LayoutUnit line_left_bfc_offset;
- LayoutUnit line_right_bfc_offset;
+ bool use_first_line_style;
// We don't create "certain zero-height line boxes".
// https://drafts.csswg.org/css2/visuren.html#phantom-line-box
@@ -85,16 +92,20 @@ class CORE_EXPORT NGLineBreaker {
bool is_after_forced_break = false;
LayoutUnit AvailableWidth() const {
- DCHECK_GE(line_right_bfc_offset, line_left_bfc_offset);
- return line_right_bfc_offset - line_left_bfc_offset;
+ return line_opportunity.AvailableInlineSize();
}
bool CanFit() const { return position <= AvailableWidth(); }
bool CanFit(LayoutUnit extra) const {
return position + extra <= AvailableWidth();
}
+ bool CanFloatFit(LayoutUnit extra) const {
+ return position + extra <= line_opportunity.AvailableFloatInlineSize();
+ }
};
- const String& Text() const { return break_iterator_.GetString(); }
+ const String& Text() const { return items_data_.text_content; }
+ const Vector<NGInlineItem>& Items() const { return items_data_.items; }
+
NGInlineItemResult* AddItem(const NGInlineItem&,
unsigned end_offset,
NGInlineItemResults*);
@@ -104,7 +115,7 @@ class CORE_EXPORT NGLineBreaker {
void BreakLine(NGLineInfo*);
- void PrepareNextLine(const NGLayoutOpportunity&, NGLineInfo*);
+ void PrepareNextLine(const NGLineLayoutOpportunity&, NGLineInfo*);
void UpdatePosition(const NGInlineItemResults&);
void ComputeLineLocation(NGLineInfo*) const;
@@ -128,6 +139,7 @@ class CORE_EXPORT NGLineBreaker {
LayoutUnit available_width,
NGLineInfo*);
LineBreakState HandleTrailingSpaces(const NGInlineItem&, NGLineInfo*);
+ void RemoveTrailingCollapsibleSpace(NGLineInfo*);
void AppendHyphen(const NGInlineItem& item, NGLineInfo*);
LineBreakState HandleControlItem(const NGInlineItem&,
@@ -137,7 +149,7 @@ class CORE_EXPORT NGLineBreaker {
LineBreakState,
NGLineInfo*);
void HandleAtomicInline(const NGInlineItem&, NGLineInfo*);
- void HandleFloat(const NGInlineItem&, NGInlineItemResult*);
+ void HandleFloat(const NGInlineItem&, NGLineInfo*, NGInlineItemResult*);
void HandleOpenTag(const NGInlineItem&, NGInlineItemResult*);
void HandleCloseTag(const NGInlineItem&, NGInlineItemResults*);
@@ -147,33 +159,33 @@ class CORE_EXPORT NGLineBreaker {
void Rewind(NGLineInfo*, unsigned new_end);
LayoutObject* CurrentLayoutObject(const NGLineInfo&) const;
- void TruncateOverflowingText(NGLineInfo*);
void SetCurrentStyle(const ComputedStyle&);
void MoveToNextOf(const NGInlineItem&);
void MoveToNextOf(const NGInlineItemResult&);
- bool IsFirstFormattedLine() const;
- void ComputeBaseDirection();
+ void ComputeBaseDirection(const NGLineInfo&);
+ bool IsTrailing(const NGInlineItem&, const NGLineInfo&) const;
LineData line_;
NGInlineNode node_;
+ const NGInlineItemsData& items_data_;
+
NGLineBreakerMode mode_;
const NGConstraintSpace& constraint_space_;
Vector<NGPositionedFloat>* positioned_floats_;
Vector<scoped_refptr<NGUnpositionedFloat>>* unpositioned_floats_;
+ NGContainerFragmentBuilder* container_builder_; /* May be nullptr */
NGExclusionSpace* exclusion_space_;
scoped_refptr<const ComputedStyle> current_style_;
unsigned item_index_ = 0;
unsigned offset_ = 0;
- bool previous_line_had_forced_break_ = false;
- LayoutUnit bfc_line_offset_;
- LayoutUnit bfc_block_offset_;
LazyLineBreakIterator break_iterator_;
HarfBuzzShaper shaper_;
ShapeResultSpacing<String> spacing_;
+ bool previous_line_had_forced_break_ = false;
const Hyphenation* hyphenation_ = nullptr;
// Keep track of handled float items. See HandleFloat().
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 9cf39096049..30c5402b812 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
@@ -11,6 +11,7 @@
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
@@ -46,15 +47,14 @@ class NGLineBreakerTest : public NGBaseLayoutAlgorithmTest {
Vector<NGInlineItemResults> lines;
NGExclusionSpace exclusion_space;
- NGLayoutOpportunity opportunity;
- opportunity.rect =
- NGBfcRect(NGBfcOffset(), {available_width, LayoutUnit::Max()});
+ NGLineLayoutOpportunity line_opportunity(available_width);
NGLineInfo line_info;
while (!break_token || !break_token->IsFinished()) {
NGLineBreaker line_breaker(node, NGLineBreakerMode::kContent, *space,
&positioned_floats, &unpositioned_floats,
+ /* container_builder */ nullptr,
&exclusion_space, 0u, break_token.get());
- if (!line_breaker.NextLine(opportunity, &line_info))
+ if (!line_breaker.NextLine(line_opportunity, &line_info))
break;
break_token = line_breaker.CreateBreakToken(line_info, nullptr);
@@ -69,8 +69,11 @@ namespace {
String ToString(NGInlineItemResults line, NGInlineNode node) {
StringBuilder builder;
+ const String& text = node.ItemsData(false).text_content;
for (const auto& item_result : line) {
- builder.Append(node.Text(item_result.start_offset, item_result.end_offset));
+ builder.Append(
+ StringView(text, item_result.start_offset,
+ item_result.end_offset - item_result.start_offset));
}
return builder.ToString();
}
@@ -181,7 +184,7 @@ TEST_F(NGLineBreakerTest, OverflowMargin) {
</style>
<div id=container><span>123 456</span> 789</div>
)HTML");
- const Vector<NGInlineItem>& items = node.Items();
+ const Vector<NGInlineItem>& items = node.ItemsData(false).items;
// While "123 456" can fit in a line, "456" has a right margin that cannot
// fit. Since "456" and its right margin is not breakable, "456" should be on
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.cc
index 44d25987ad9..b83d812d685 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.cc
@@ -15,6 +15,9 @@ NGLineHeightMetrics::NGLineHeightMetrics(const ComputedStyle& style,
Initialize(font_data->GetFontMetrics(), baseline_type);
}
+NGLineHeightMetrics::NGLineHeightMetrics(const ComputedStyle& style)
+ : NGLineHeightMetrics(style, style.GetFontBaseline()) {}
+
NGLineHeightMetrics::NGLineHeightMetrics(const FontMetrics& font_metrics,
FontBaseline baseline_type) {
Initialize(font_metrics, baseline_type);
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h
index 8c8c57dc515..978c170044e 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h
@@ -25,6 +25,7 @@ struct NGLineHeightMetrics {
// Compute from ComputedStyle, using the font metrics of the prikmary font.
// The leading is not included.
+ NGLineHeightMetrics(const ComputedStyle&);
NGLineHeightMetrics(const ComputedStyle&, FontBaseline);
// Compute from FontMetrics. The leading is not included.
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
new file mode 100644
index 00000000000..ae76600c718
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
@@ -0,0 +1,201 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h"
+
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h"
+#include "third_party/blink/renderer/platform/fonts/font_baseline.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h"
+
+namespace blink {
+
+namespace {
+
+// Create the style to use for the ellipsis characters.
+//
+// The ellipsis is styled according to the line style.
+// https://drafts.csswg.org/css-ui/#ellipsing-details
+scoped_refptr<const ComputedStyle> CreateEllipsisStyle(
+ scoped_refptr<const ComputedStyle> line_style) {
+ if (line_style->TextDecorationsInEffect() == TextDecoration::kNone)
+ return line_style;
+
+ // Ellipsis should not have text decorations. Reset if it's set.
+ // This is not defined, but 4 impls do this.
+ scoped_refptr<ComputedStyle> ellipsis_style =
+ ComputedStyle::CreateAnonymousStyleWithDisplay(*line_style,
+ EDisplay::kInline);
+ ellipsis_style->ResetTextDecoration();
+ ellipsis_style->ClearAppliedTextDecorations();
+ return ellipsis_style;
+}
+
+} // namespace
+
+NGLineTruncator::NGLineTruncator(NGInlineNode& node,
+ const NGLineInfo& line_info)
+ : node_(node),
+ line_style_(&line_info.LineStyle()),
+ available_width_(line_info.AvailableWidth()),
+ line_direction_(line_info.BaseDirection()) {}
+
+LayoutUnit NGLineTruncator::TruncateLine(
+ LayoutUnit line_width,
+ NGLineBoxFragmentBuilder::ChildList* line_box) {
+ // Shape the ellipsis and compute its inline size.
+ scoped_refptr<const ComputedStyle> ellipsis_style =
+ CreateEllipsisStyle(line_style_);
+ const Font& font = ellipsis_style->GetFont();
+ const SimpleFontData* font_data = font.PrimaryFont();
+ DCHECK(font_data);
+ String ellipsis_text =
+ font_data && font_data->GlyphForCharacter(kHorizontalEllipsisCharacter)
+ ? String(&kHorizontalEllipsisCharacter, 1)
+ : String(u"...");
+ HarfBuzzShaper shaper(ellipsis_text.Characters16(), ellipsis_text.length());
+ scoped_refptr<ShapeResult> ellipsis_shape_result =
+ shaper.Shape(&font, line_direction_);
+ LayoutUnit ellipsis_width = ellipsis_shape_result->SnappedWidth();
+
+ // Loop children from the logical last to the logical first to determine where
+ // to place the ellipsis. Children maybe truncated or moved as part of the
+ // process.
+ LayoutUnit ellipsis_inline_offset;
+ const NGPhysicalFragment* ellipsized_fragment = nullptr;
+ if (IsLtr(line_direction_)) {
+ NGLineBoxFragmentBuilder::Child* first_child = line_box->FirstInFlowChild();
+ for (auto it = line_box->rbegin(); it != line_box->rend(); it++) {
+ auto& child = *it;
+ if (base::Optional<LayoutUnit> candidate = EllipsisOffset(
+ line_width, ellipsis_width, &child == first_child, &child)) {
+ ellipsis_inline_offset = candidate.value();
+ ellipsized_fragment = child.PhysicalFragment();
+ DCHECK(ellipsized_fragment);
+ break;
+ }
+ }
+ } else {
+ NGLineBoxFragmentBuilder::Child* first_child = line_box->LastInFlowChild();
+ ellipsis_inline_offset = available_width_ - ellipsis_width;
+ for (auto& child : *line_box) {
+ if (base::Optional<LayoutUnit> candidate = EllipsisOffset(
+ line_width, ellipsis_width, &child == first_child, &child)) {
+ ellipsis_inline_offset = candidate.value();
+ ellipsized_fragment = child.PhysicalFragment();
+ DCHECK(ellipsized_fragment);
+ break;
+ }
+ }
+ }
+
+ // Abort if ellipsis could not be placed.
+ if (!ellipsized_fragment)
+ return line_width;
+
+ // Now the offset of the ellpisis is determined. Place the ellpisis into the
+ // line box.
+ NGTextFragmentBuilder builder(node_, line_style_->GetWritingMode());
+ DCHECK(ellipsized_fragment->GetLayoutObject() &&
+ ellipsized_fragment->GetLayoutObject()->IsInline());
+ builder.SetText(ellipsized_fragment->GetLayoutObject(), ellipsis_text,
+ ellipsis_style, true /* is_ellipsis_style */,
+ std::move(ellipsis_shape_result));
+ FontBaseline baseline_type = line_style_->GetFontBaseline();
+ NGLineHeightMetrics ellipsis_metrics(font_data->GetFontMetrics(),
+ baseline_type);
+ line_box->AddChild(
+ builder.ToTextFragment(),
+ NGLogicalOffset{ellipsis_inline_offset, -ellipsis_metrics.ascent},
+ ellipsis_width, 0);
+ return std::max(ellipsis_inline_offset + ellipsis_width, line_width);
+}
+
+// Return the offset to place the ellipsis.
+//
+// This function may truncate or move the child so that the ellipsis can fit.
+base::Optional<LayoutUnit> NGLineTruncator::EllipsisOffset(
+ LayoutUnit line_width,
+ LayoutUnit ellipsis_width,
+ bool is_first_child,
+ NGLineBoxFragmentBuilder::Child* child) {
+ // Leave out-of-flow children as is.
+ if (!child->HasInFlowFragment())
+ return base::nullopt;
+
+ // Can't place ellipsis if this child is completely outside of the box.
+ DCHECK_GE(line_width, child->offset.inline_offset + child->inline_size);
+ LayoutUnit child_inline_offset =
+ IsLtr(line_direction_)
+ ? child->offset.inline_offset
+ : line_width - (child->offset.inline_offset + child->inline_size);
+ LayoutUnit space_for_child = available_width_ - child_inline_offset;
+ if (space_for_child <= 0)
+ return base::nullopt;
+
+ // If not all of this child can fit, try to truncate.
+ space_for_child -= ellipsis_width;
+ if (space_for_child < child->inline_size &&
+ !TruncateChild(space_for_child, is_first_child, child)) {
+ // This child maybe partially visible. When it can't be truncated, move it
+ // out so that none of this child should be visible.
+ child->offset.inline_offset = line_width;
+ return base::nullopt;
+ }
+
+ return IsLtr(line_direction_)
+ ? child->offset.inline_offset + child->inline_size
+ : child->offset.inline_offset - ellipsis_width;
+}
+
+// Truncate the specified child. Returns true if truncated successfully, false
+// otherwise.
+//
+// Note that this function may return true even if it can't fit the child when
+// |is_first_child|, because the spec defines that the first character or atomic
+// inline-level element on a line must be clipped rather than ellipsed.
+// https://drafts.csswg.org/css-ui/#text-overflow
+bool NGLineTruncator::TruncateChild(LayoutUnit space_for_child,
+ bool is_first_child,
+ NGLineBoxFragmentBuilder::Child* child) {
+ // If the space is not enough, try the next child.
+ if (space_for_child <= 0 && !is_first_child)
+ return false;
+
+ // Only text fragments can be truncated.
+ if (!child->fragment)
+ return is_first_child;
+ auto& fragment = ToNGPhysicalTextFragment(*child->fragment);
+ const ShapeResult* shape_result = fragment.TextShapeResult();
+ if (!shape_result)
+ return is_first_child;
+
+ // Compute the offset to truncate.
+ unsigned new_length = shape_result->OffsetToFit(
+ IsLtr(line_direction_) ? space_for_child
+ : shape_result->Width() - space_for_child,
+ line_direction_);
+ if (!new_length || new_length == fragment.Length()) {
+ if (!is_first_child)
+ return false;
+ new_length = !new_length ? 1 : new_length - 1;
+ }
+
+ // Truncate the text fragment.
+ child->fragment = line_direction_ == shape_result->Direction()
+ ? fragment.TrimText(fragment.StartOffset(),
+ fragment.StartOffset() + new_length)
+ : fragment.TrimText(fragment.StartOffset() + new_length,
+ fragment.EndOffset());
+ LayoutUnit new_inline_size = line_style_->IsHorizontalWritingMode()
+ ? child->fragment->Size().width
+ : child->fragment->Size().height;
+ DCHECK_LE(new_inline_size, child->inline_size);
+ if (UNLIKELY(IsRtl(line_direction_)))
+ child->offset.inline_offset += child->inline_size - new_inline_size;
+ child->inline_size = new_inline_size;
+ return true;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h
new file mode 100644
index 00000000000..adfcd22ad9c
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h
@@ -0,0 +1,51 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_TRUNCATOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_TRUNCATOR_H_
+
+#include "base/optional.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h"
+#include "third_party/blink/renderer/platform/text/text_direction.h"
+
+namespace blink {
+
+class NGLineInfo;
+
+// A class to truncate lines and place ellipsis, invoked by the CSS
+// 'text-overflow: ellipsis' property.
+// https://drafts.csswg.org/css-ui/#overflow-ellipsis
+class CORE_EXPORT NGLineTruncator final {
+ STACK_ALLOCATED();
+
+ public:
+ NGLineTruncator(NGInlineNode& node, const NGLineInfo& line_info);
+
+ // Truncate |line_box| and place ellipsis. Returns the new inline-size of the
+ // |line_box|.
+ //
+ // |line_box| should be after bidi reorder, but before box fragments are
+ // created.
+ LayoutUnit TruncateLine(LayoutUnit line_width,
+ NGLineBoxFragmentBuilder::ChildList* line_box);
+
+ private:
+ base::Optional<LayoutUnit> EllipsisOffset(LayoutUnit line_width,
+ LayoutUnit ellipsis_width,
+ bool is_first_child,
+ NGLineBoxFragmentBuilder::Child*);
+ bool TruncateChild(LayoutUnit space_for_this_child,
+ bool is_first_child,
+ NGLineBoxFragmentBuilder::Child* child);
+
+ NGInlineNode& node_;
+ scoped_refptr<const ComputedStyle> line_style_;
+ LayoutUnit available_width_;
+ TextDirection line_direction_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_TRUNCATOR_H_
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_utils.cc
new file mode 100644
index 00000000000..d4c9f60a712
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_utils.cc
@@ -0,0 +1,31 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_utils.h"
+
+#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h"
+#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
+
+namespace blink {
+
+const NGPaintFragment* NGContainingLineBoxOf(
+ const PositionWithAffinity& position) {
+ const NGCaretPosition caret_position = ComputeNGCaretPosition(position);
+ if (caret_position.IsNull())
+ return nullptr;
+ return caret_position.fragment->ContainerLineBox();
+}
+
+bool InSameNGLineBox(const PositionWithAffinity& position1,
+ const PositionWithAffinity& position2) {
+ const NGPaintFragment* line_box1 = NGContainingLineBoxOf(position1);
+ if (!line_box1)
+ return false;
+
+ const NGPaintFragment* line_box2 = NGContainingLineBoxOf(position2);
+ return line_box1 == line_box2;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_utils.h
new file mode 100644
index 00000000000..e77e8dbde26
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_utils.h
@@ -0,0 +1,25 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_UTILS_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_UTILS_H_
+
+#include "third_party/blink/renderer/core/editing/forward.h"
+
+namespace blink {
+
+class NGPaintFragment;
+
+// Returns the NG line box fragment containing the caret position of the given
+// position. Returns false if the position is not in Layout NG, or does not
+// have any caret position.
+const NGPaintFragment* NGContainingLineBoxOf(const PositionWithAffinity&);
+
+// Returns true if the caret positions of the two positions are in the same NG
+// line box. Returns false in all other cases.
+bool InSameNGLineBox(const PositionWithAffinity&, const PositionWithAffinity&);
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_UTILS_H_
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
index ff4406cef52..46e7077f983 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
@@ -10,6 +10,7 @@
#include "third_party/blink/renderer/core/editing/position.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
+#include "third_party/blink/renderer/platform/text/character.h"
namespace blink {
@@ -247,13 +248,13 @@ NGMappingUnitRange NGOffsetMapping::GetMappingUnitsForDOMRange(
return {result_begin, result_end};
}
-Optional<unsigned> NGOffsetMapping::GetTextContentOffset(
+base::Optional<unsigned> NGOffsetMapping::GetTextContentOffset(
const Position& position) const {
DCHECK(NGOffsetMapping::AcceptsPosition(position)) << position;
if (IsNonAtomicInline(*position.AnchorNode())) {
auto iter = ranges_.find(position.AnchorNode());
if (iter == ranges_.end())
- return WTF::nullopt;
+ return base::nullopt;
DCHECK_NE(iter->value.first, iter->value.second) << position;
if (position.IsBeforeAnchor())
return units_[iter->value.first].TextContentStart();
@@ -262,7 +263,7 @@ Optional<unsigned> NGOffsetMapping::GetTextContentOffset(
const NGOffsetMappingUnit* unit = GetMappingUnitForPosition(position);
if (!unit)
- return WTF::nullopt;
+ return base::nullopt;
return unit->ConvertDOMOffsetToTextContent(ToNodeOffsetPair(position).second);
}
@@ -339,13 +340,13 @@ bool NGOffsetMapping::IsAfterNonCollapsedContent(
unit->GetType() != NGOffsetMappingUnitType::kCollapsed;
}
-Optional<UChar> NGOffsetMapping::GetCharacterBefore(
+base::Optional<UChar> NGOffsetMapping::GetCharacterBefore(
const Position& position) const {
DCHECK(NGOffsetMapping::AcceptsPosition(position));
DCHECK(!IsNonAtomicInline(*position.AnchorNode())) << position;
- Optional<unsigned> text_content_offset = GetTextContentOffset(position);
+ base::Optional<unsigned> text_content_offset = GetTextContentOffset(position);
if (!text_content_offset || !*text_content_offset)
- return WTF::nullopt;
+ return base::nullopt;
return text_[*text_content_offset - 1];
}
@@ -385,4 +386,15 @@ Position NGOffsetMapping::GetLastPosition(unsigned offset) const {
return CreatePositionForOffsetMapping(node, dom_offset);
}
+bool NGOffsetMapping::HasBidiControlCharactersOnly(unsigned start,
+ unsigned end) const {
+ DCHECK_LE(start, end);
+ DCHECK_LE(end, text_.length());
+ for (unsigned i = start; i < end; ++i) {
+ if (!Character::IsBidiControl(text_[i]))
+ return false;
+ }
+ return true;
+}
+
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h
index f4ea52ba8f5..efb2769eb30 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h
@@ -5,12 +5,13 @@
#ifndef NGOffsetMapping_h
#define NGOffsetMapping_h
+#include "base/optional.h"
#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/editing/forward.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/wtf/allocator.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
-#include "third_party/blink/renderer/platform/wtf/optional.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
@@ -18,7 +19,6 @@ namespace blink {
class LayoutBlockFlow;
class LayoutObject;
-class Node;
enum class NGOffsetMappingUnitType { kIdentity, kCollapsed, kExpanded };
@@ -134,7 +134,7 @@ class CORE_EXPORT NGOffsetMapping {
// Returns the text content offset corresponding to the given position.
// Returns nullopt when the position is not laid out in this context.
- Optional<unsigned> GetTextContentOffset(const Position&) const;
+ base::Optional<unsigned> GetTextContentOffset(const Position&) const;
// Starting from the given position, searches for non-collapsed content in
// the anchor node in forward/backward direction and returns the position
@@ -151,7 +151,7 @@ class CORE_EXPORT NGOffsetMapping {
// Maps the given position to a text content offset, and then returns the text
// content character before the offset. Returns nullopt if it does not exist.
- Optional<UChar> GetCharacterBefore(const Position&) const;
+ base::Optional<UChar> GetCharacterBefore(const Position&) const;
// ------ Mapping APIs from text content to DOM ------
@@ -172,6 +172,12 @@ class CORE_EXPORT NGOffsetMapping {
// TODO(xiaochengh): Add offset-to-DOM APIs skipping generated contents.
+ // ------ APIs inspecting the text content string ------
+
+ // Returns false if all characters in [start, end) of |text_| are bidi
+ // control charcters. Returns true otherwise.
+ bool HasBidiControlCharactersOnly(unsigned start, unsigned end) const;
+
private:
// The NGOffsetMappingUnits of the inline formatting context in osrted order.
UnitVector units_;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.h
index 8451f04475a..716d3ac760e 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.h
@@ -5,9 +5,9 @@
#ifndef NGOffsetMappingBuilder_h
#define NGOffsetMappingBuilder_h
+#include "base/auto_reset.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/platform/wtf/allocator.h"
-#include "third_party/blink/renderer/platform/wtf/auto_reset.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
@@ -80,7 +80,7 @@ class CORE_EXPORT NGOffsetMappingBuilder {
~SourceNodeScope();
private:
- AutoReset<const LayoutObject*> auto_reset_;
+ base::AutoReset<const LayoutObject*> auto_reset_;
#if DCHECK_IS_ON()
NGOffsetMappingBuilder* builder_ = nullptr;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc
index 39232f8ad19..1036a000106 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc
@@ -40,7 +40,7 @@ class NGOffsetMappingTest : public NGLayoutTest {
}
bool IsOffsetMappingStored() const {
- return layout_block_flow_->GetNGInlineNodeData()->offset_mapping_.get();
+ return layout_block_flow_->GetNGInlineNodeData()->offset_mapping.get();
}
const LayoutText* GetLayoutTextUnder(const char* parent_id) {
@@ -53,7 +53,8 @@ class NGOffsetMappingTest : public NGLayoutTest {
return GetOffsetMapping().GetMappingUnitForPosition(position);
}
- Optional<unsigned> GetTextContentOffset(const Position& position) const {
+ base::Optional<unsigned> GetTextContentOffset(
+ const Position& position) const {
return GetOffsetMapping().GetTextContentOffset(position);
}
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
index 10d79eb6c43..e341ddffdae 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
@@ -5,26 +5,61 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
namespace blink {
+namespace {
+
+static const NGPhysicalFragment* LastLogicalLeafExceptLinebreakInternal(
+ const NGPhysicalFragment& runner,
+ TextDirection direction) {
+ if (runner.IsText()) {
+ if (ToNGPhysicalTextFragment(runner).IsLineBreak())
+ return nullptr;
+ return &runner;
+ }
+ if (!runner.IsContainer() || runner.IsBlockLayoutRoot())
+ return &runner;
+ const auto& children = ToNGPhysicalContainerFragment(runner).Children();
+ for (size_t i = 0; i < children.size(); i++) {
+ // TODO(xiaochengh): This isn't correct for mixed Bidi. Fix it. Besides, we
+ // should compute and store it during layout.
+ // We want a logical last child in a line.
+ const size_t index =
+ direction == TextDirection::kLtr ? (children.size() - 1 - i) : i;
+ const NGPhysicalFragment* child = children[index].get();
+ DCHECK(child);
+ if (const NGPhysicalFragment* candidate =
+ LastLogicalLeafExceptLinebreakInternal(*child, direction))
+ return candidate;
+ }
+ return nullptr;
+}
+
+} // namespace
+
NGPhysicalLineBoxFragment::NGPhysicalLineBoxFragment(
const ComputedStyle& style,
+ NGStyleVariant style_variant,
NGPhysicalSize size,
Vector<scoped_refptr<NGPhysicalFragment>>& children,
const NGPhysicalOffsetRect& contents_visual_rect,
+ const NGPhysicalOffsetRect& scrollable_overflow,
const NGLineHeightMetrics& metrics,
TextDirection base_direction,
scoped_refptr<NGBreakToken> break_token)
: NGPhysicalContainerFragment(nullptr,
style,
+ style_variant,
size,
kFragmentLineBox,
0,
children,
contents_visual_rect,
std::move(break_token)),
+ scrollable_overflow_(scrollable_overflow),
metrics_(metrics) {
base_direction_ = static_cast<unsigned>(base_direction);
}
@@ -80,6 +115,13 @@ const NGPhysicalFragment* NGPhysicalLineBoxFragment::LastLogicalLeaf() const {
return runner;
}
+const NGPhysicalFragment*
+NGPhysicalLineBoxFragment::LastLogicalLeafIgnoringLineBreak() const {
+ if (Children().IsEmpty())
+ return nullptr;
+ return LastLogicalLeafExceptLinebreakInternal(*this, this->BaseDirection());
+}
+
bool NGPhysicalLineBoxFragment::HasSoftWrapToNextLine() const {
DCHECK(BreakToken());
DCHECK(BreakToken()->IsInlineType());
@@ -87,9 +129,4 @@ bool NGPhysicalLineBoxFragment::HasSoftWrapToNextLine() const {
return !break_token.IsFinished() && !break_token.IsForcedBreak();
}
-PositionWithAffinity NGPhysicalLineBoxFragment::PositionForPoint(
- const NGPhysicalOffset& point) const {
- return PositionForPointInInlineLevelBox(point);
-}
-
} // namespace blink
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 02989a0955e..b92b910d2b3 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
@@ -17,9 +17,11 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final
public:
// This modifies the passed-in children vector.
NGPhysicalLineBoxFragment(const ComputedStyle&,
+ NGStyleVariant style_variant,
NGPhysicalSize size,
Vector<scoped_refptr<NGPhysicalFragment>>& children,
const NGPhysicalOffsetRect& contents_visual_rect,
+ const NGPhysicalOffsetRect& scrollable_overflow,
const NGLineHeightMetrics&,
TextDirection base_direction,
scoped_refptr<NGBreakToken> break_token = nullptr);
@@ -39,24 +41,31 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final
// VisualRect of itself including contents, in the local coordinate.
NGPhysicalOffsetRect VisualRectWithContents() const;
+ // Scrollable overflow. including contents, in the local coordinate.
+ NGPhysicalOffsetRect ScrollableOverflow() const {
+ return scrollable_overflow_;
+ }
+
// Returns the first/last leaf fragment in the line in logical order. Returns
// nullptr if the line box is empty.
const NGPhysicalFragment* FirstLogicalLeaf() const;
const NGPhysicalFragment* LastLogicalLeaf() const;
+ // Returns the last leaf fragment in the line in logical order except line
+ // break. Returns nullptr if such fragment doesn't exist.
+ const NGPhysicalFragment* LastLogicalLeafIgnoringLineBreak() const;
// Whether the content soft-wraps to the next line.
bool HasSoftWrapToNextLine() const;
- PositionWithAffinity PositionForPoint(const NGPhysicalOffset&) const final;
-
scoped_refptr<NGPhysicalFragment> CloneWithoutOffset() const {
Vector<scoped_refptr<NGPhysicalFragment>> children_copy(children_);
return base::AdoptRef(new NGPhysicalLineBoxFragment(
- Style(), size_, children_copy, contents_visual_rect_, metrics_,
- BaseDirection(), break_token_));
+ Style(), StyleVariant(), size_, children_copy, contents_visual_rect_,
+ scrollable_overflow_, metrics_, BaseDirection(), break_token_));
}
private:
+ NGPhysicalOffsetRect scrollable_overflow_;
NGLineHeightMetrics metrics_;
};
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc
index cd44fd8ea05..331fb87d70b 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc
@@ -59,6 +59,7 @@ TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafInSimpleText) {
"</div>");
EXPECT_TEXT_FRAGMENT("foo", GetLineBox()->FirstLogicalLeaf());
EXPECT_TEXT_FRAGMENT("bar", GetLineBox()->LastLogicalLeaf());
+ EXPECT_TEXT_FRAGMENT("bar", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
}
TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafInRtlText) {
@@ -69,6 +70,7 @@ TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafInRtlText) {
"</bdo>");
EXPECT_TEXT_FRAGMENT("foo", GetLineBox()->FirstLogicalLeaf());
EXPECT_TEXT_FRAGMENT("bar", GetLineBox()->LastLogicalLeaf());
+ EXPECT_TEXT_FRAGMENT("bar", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
}
TEST_F(NGPhysicalLineBoxFragmentTest,
@@ -81,6 +83,7 @@ TEST_F(NGPhysicalLineBoxFragmentTest,
"</div>");
EXPECT_TEXT_FRAGMENT("f", GetLineBox()->FirstLogicalLeaf());
EXPECT_TEXT_FRAGMENT("r", GetLineBox()->LastLogicalLeaf());
+ EXPECT_TEXT_FRAGMENT("r", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
}
TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafWithInlineBlock) {
@@ -92,12 +95,26 @@ TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafWithInlineBlock) {
"</div>");
EXPECT_BOX_FRAGMENT("foo", GetLineBox()->FirstLogicalLeaf());
EXPECT_BOX_FRAGMENT("baz", GetLineBox()->LastLogicalLeaf());
+ EXPECT_BOX_FRAGMENT("baz", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
}
TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafWithImages) {
SetBodyInnerHTML("<div id=root><img id=img1>foo<img id=img2></div>");
EXPECT_BOX_FRAGMENT("img1", GetLineBox()->FirstLogicalLeaf());
EXPECT_BOX_FRAGMENT("img2", GetLineBox()->LastLogicalLeaf());
+ EXPECT_BOX_FRAGMENT("img2", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
+}
+
+TEST_F(NGPhysicalLineBoxFragmentTest, LastLogicalLeafSoftWrap) {
+ SetBodyInnerHTML("<div id=root style='width: 2em'>foo bar</div>");
+ EXPECT_TEXT_FRAGMENT("foo", GetLineBox()->LastLogicalLeaf());
+ EXPECT_TEXT_FRAGMENT("foo", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
+}
+
+TEST_F(NGPhysicalLineBoxFragmentTest, LastLogicalLeafHardWrap) {
+ SetBodyInnerHTML("<div id=root>foo<br>bar</div>");
+ EXPECT_TEXT_FRAGMENT("\n", GetLineBox()->LastLogicalLeaf());
+ EXPECT_TEXT_FRAGMENT("foo", GetLineBox()->LastLogicalLeafIgnoringLineBreak());
}
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
index 848ed377dd8..d75f591cc0f 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
@@ -5,16 +5,31 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
#include "third_party/blink/renderer/core/dom/node.h"
-#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect.h"
+#include "third_party/blink/renderer/core/layout/line/line_orientation_utils.h"
#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
namespace blink {
+// Convert logical cooridnate to local physical coordinate.
+NGPhysicalOffsetRect NGPhysicalTextFragment::ConvertToLocal(
+ const LayoutRect& logical_rect) const {
+ switch (LineOrientation()) {
+ case NGLineOrientation::kHorizontal:
+ return NGPhysicalOffsetRect(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 NGPhysicalOffsetRect(logical_rect);
+}
+
// Compute the inline position from text offset, in logical coordinate relative
// to this fragment.
LayoutUnit NGPhysicalTextFragment::InlinePositionForOffset(
@@ -75,8 +90,8 @@ NGPhysicalOffsetRect NGPhysicalTextFragment::LocalRect(
}
NGPhysicalOffsetRect NGPhysicalTextFragment::SelfVisualRect() const {
- if (!shape_result_)
- return {};
+ if (UNLIKELY(!shape_result_))
+ return LocalRect();
// Glyph bounds is in logical coordinate, origin at the alphabetic baseline.
LayoutRect visual_rect = EnclosingLayoutRect(shape_result_->Bounds());
@@ -84,8 +99,7 @@ NGPhysicalOffsetRect NGPhysicalTextFragment::SelfVisualRect() const {
// Make the origin at the logical top of this fragment.
const ComputedStyle& style = Style();
const Font& font = style.GetFont();
- const SimpleFontData* font_data = font.PrimaryFont();
- if (font_data) {
+ if (const SimpleFontData* font_data = font.PrimaryFont()) {
visual_rect.SetY(visual_rect.Y() + font_data->GetFontMetrics().FixedAscent(
kAlphabeticBaseline));
}
@@ -111,26 +125,38 @@ NGPhysicalOffsetRect NGPhysicalTextFragment::SelfVisualRect() const {
if (ShadowList* text_shadow = style.TextShadow()) {
LayoutRectOutsets text_shadow_logical_outsets =
- LayoutRectOutsets(text_shadow->RectOutsetsIncludingOriginal())
- .LineOrientationOutsets(style.GetWritingMode());
+ LineOrientationLayoutRectOutsets(
+ LayoutRectOutsets(text_shadow->RectOutsetsIncludingOriginal()),
+ style.GetWritingMode());
text_shadow_logical_outsets.ClampNegativeToZero();
visual_rect.Expand(text_shadow_logical_outsets);
}
visual_rect = LayoutRect(EnclosingIntRect(visual_rect));
- switch (LineOrientation()) {
- case NGLineOrientation::kHorizontal:
- return NGPhysicalOffsetRect(visual_rect);
- case NGLineOrientation::kClockWiseVertical:
- return {{size_.width - visual_rect.MaxY(), visual_rect.X()},
- {visual_rect.Height(), visual_rect.Width()}};
- case NGLineOrientation::kCounterClockWiseVertical:
- return {{visual_rect.Y(), size_.height - visual_rect.MaxX()},
- {visual_rect.Height(), visual_rect.Width()}};
- }
- NOTREACHED();
- return {};
+ // Uniting the frame rect ensures that non-ink spaces such side bearings, or
+ // even space characters, are included in the visual rect for decorations.
+ NGPhysicalOffsetRect local_visual_rect = ConvertToLocal(visual_rect);
+ local_visual_rect.Unite(LocalRect());
+ return local_visual_rect;
+}
+
+scoped_refptr<NGPhysicalFragment> NGPhysicalTextFragment::TrimText(
+ unsigned new_start_offset,
+ unsigned new_end_offset) const {
+ DCHECK(shape_result_);
+ DCHECK_GE(new_start_offset, StartOffset());
+ DCHECK_GT(new_end_offset, new_start_offset);
+ DCHECK_LE(new_end_offset, EndOffset());
+ scoped_refptr<ShapeResult> new_shape_result =
+ shape_result_->SubRange(new_start_offset, new_end_offset);
+ LayoutUnit new_inline_size = new_shape_result->SnappedWidth();
+ return base::AdoptRef(new NGPhysicalTextFragment(
+ layout_object_, Style(), static_cast<NGStyleVariant>(style_variant_),
+ TextType(), text_, new_start_offset, new_end_offset,
+ IsHorizontal() ? NGPhysicalSize{new_inline_size, size_.height}
+ : NGPhysicalSize{size_.width, new_inline_size},
+ LineOrientation(), EndEffect(), std::move(new_shape_result)));
}
scoped_refptr<NGPhysicalFragment> NGPhysicalTextFragment::CloneWithoutOffset()
@@ -164,15 +190,24 @@ unsigned NGPhysicalTextFragment::TextOffsetForPoint(
StartOffset();
}
-PositionWithAffinity NGPhysicalTextFragment::PositionForPoint(
- const NGPhysicalOffset& point) const {
- if (IsAnonymousText())
- return PositionWithAffinity();
- const unsigned text_offset = TextOffsetForPoint(point);
- const Position position =
- NGOffsetMapping::GetFor(GetLayoutObject())->GetFirstPosition(text_offset);
- // TODO(xiaochengh): Adjust TextAffinity.
- return PositionWithAffinity(position, TextAffinity::kDownstream);
+UBiDiLevel NGPhysicalTextFragment::BidiLevel() const {
+ // TODO(xiaochengh): Make the implementation more efficient with, e.g.,
+ // binary search and/or LayoutNGText::InlineItems().
+ const auto& items = InlineItemsOfContainingBlock();
+ const NGInlineItem* containing_item = std::find_if(
+ items.begin(), items.end(), [this](const NGInlineItem& item) {
+ return item.StartOffset() <= StartOffset() &&
+ item.EndOffset() >= EndOffset();
+ });
+ DCHECK(containing_item);
+ DCHECK_NE(containing_item, items.end());
+ return containing_item->BidiLevel();
+}
+
+TextDirection NGPhysicalTextFragment::ResolvedDirection() const {
+ if (TextShapeResult())
+ return TextShapeResult()->Direction();
+ return NGPhysicalFragment::ResolvedDirection();
}
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
index eea1dddd920..f6921d10546 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
@@ -8,7 +8,6 @@
#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_physical_fragment.h"
-#include "third_party/blink/renderer/platform/fonts/font_baseline.h"
#include "third_party/blink/renderer/platform/fonts/ng_text_fragment_paint_info.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
#include "third_party/blink/renderer/platform/wtf/text/string_view.h"
@@ -16,8 +15,6 @@
namespace blink {
-class ShapeResult;
-
struct NGPhysicalOffsetRect;
enum class AdjustMidCluster;
@@ -104,9 +101,6 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
bool IsHorizontal() const {
return LineOrientation() == NGLineOrientation::kHorizontal;
}
- FontBaseline BaselineType() const {
- return IsHorizontal() ? kAlphabeticBaseline : kIdeographicBaseline;
- }
// Compute the inline position from text offset, in logical coordinate
// relative to this fragment.
@@ -116,6 +110,7 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
// Start and end offsets must be between StartOffset() and EndOffset().
NGPhysicalOffsetRect LocalRect(unsigned start_offset,
unsigned end_offset) const;
+ using NGPhysicalFragment::LocalRect;
// The visual bounding box that includes glpyh bounding box and CSS
// properties, in local coordinates.
@@ -125,6 +120,11 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
return static_cast<NGTextEndEffect>(end_effect_);
}
+ // Create a new fragment that has part of the text of this fragment.
+ // All other properties are the same as this fragment.
+ scoped_refptr<NGPhysicalFragment> TrimText(unsigned start_offset,
+ unsigned end_offset) const;
+
scoped_refptr<NGPhysicalFragment> CloneWithoutOffset() const;
NGTextFragmentPaintInfo PaintInfo() const {
@@ -139,13 +139,16 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
// Returns the text offset in the fragment placed closest to the given point.
unsigned TextOffsetForPoint(const NGPhysicalOffset&) const;
- PositionWithAffinity PositionForPoint(const NGPhysicalOffset&) const override;
+ UBiDiLevel BidiLevel() const override;
+ TextDirection ResolvedDirection() const override;
private:
LayoutUnit InlinePositionForOffset(unsigned offset,
LayoutUnit (*round)(float),
AdjustMidCluster) const;
+ NGPhysicalOffsetRect ConvertToLocal(const LayoutRect&) 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.
const String text_;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc
index 8852bb2da74..9ee319a8d3c 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc
@@ -37,13 +37,14 @@ NGTextFragmentBuilder::NGTextFragmentBuilder(NGInlineNode node,
void NGTextFragmentBuilder::SetItem(
NGPhysicalTextFragment::NGTextType text_type,
+ const NGInlineItemsData& items_data,
NGInlineItemResult* item_result,
LayoutUnit line_height) {
DCHECK(item_result);
DCHECK(item_result->item->Style());
text_type_ = text_type;
- text_ = inline_node_.Text();
+ text_ = items_data.text_content;
item_index_ = item_result->item_index;
start_offset_ = item_result->start_offset;
end_offset_ = item_result->end_offset;
@@ -71,11 +72,8 @@ void NGTextFragmentBuilder::SetText(
end_offset_ = shape_result->EndIndexForResult();
SetStyle(style, is_ellipsis_style ? NGStyleVariant::kEllipsis
: NGStyleVariant::kStandard);
- FontBaseline baseline_type = style->IsHorizontalWritingMode()
- ? kAlphabeticBaseline
- : kIdeographicBaseline;
size_ = {shape_result->SnappedWidth(),
- NGLineHeightMetrics(*style, baseline_type).LineHeight()};
+ NGLineHeightMetrics(*style).LineHeight()};
shape_result_ = std::move(shape_result);
layout_object_ = layout_object;
end_effect_ = NGTextEndEffect::kNone;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h
index b53ca079a0b..68efb30de15 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h
@@ -26,6 +26,7 @@ class CORE_EXPORT NGTextFragmentBuilder final : public NGBaseFragmentBuilder {
// NOTE: Takes ownership of the shape result within the item result.
void SetItem(NGPhysicalTextFragment::NGTextType,
+ const NGInlineItemsData&,
NGInlineItemResult*,
LayoutUnit line_height);
void SetText(LayoutObject*,