| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h" |
| |
| #include "third_party/blink/renderer/core/editing/inline_box_traversal.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/layout/ng/inline/ng_fragment_items_builder.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.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_item_result.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" |
| #include "third_party/blink/renderer/platform/wtf/size_assertions.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| struct SameSizeAsNGFragmentItem { |
| struct { |
| void* pointer; |
| NGTextOffset text_offset; |
| } type_data; |
| PhysicalRect rect; |
| NGInkOverflow ink_overflow; |
| void* pointer; |
| wtf_size_t sizes[2]; |
| unsigned flags; |
| }; |
| |
| ASSERT_SIZE(NGFragmentItem, SameSizeAsNGFragmentItem); |
| |
| } // namespace |
| |
| NGFragmentItem::NGFragmentItem(const NGPhysicalTextFragment& text) |
| : layout_object_(text.GetLayoutObject()), |
| text_({text.TextShapeResult(), text.TextOffset()}), |
| rect_({PhysicalOffset(), text.Size()}), |
| type_(kText), |
| sub_type_(static_cast<unsigned>(text.TextType())), |
| style_variant_(static_cast<unsigned>(text.StyleVariant())), |
| is_hidden_for_paint_(text.IsHiddenForPaint()), |
| text_direction_(static_cast<unsigned>(text.ResolvedDirection())), |
| ink_overflow_type_(NGInkOverflow::kNotSet), |
| is_dirty_(false), |
| is_last_for_node_(true) { |
| #if DCHECK_IS_ON() |
| if (text_.shape_result) { |
| DCHECK_EQ(text_.shape_result->StartIndex(), StartOffset()); |
| DCHECK_EQ(text_.shape_result->EndIndex(), EndOffset()); |
| } |
| #endif |
| if (text.TextType() == NGTextType::kLayoutGenerated) { |
| type_ = kGeneratedText; |
| // Note: Because of |text_| and |generated_text_| are in same union and |
| // we initialize |text_| instead of |generated_text_|, we should construct |
| // |generated_text_.text_| instead copying, |generated_text_.text = ...|. |
| new (&generated_text_.text) String(text.Text().ToString()); |
| } |
| DCHECK(!IsFormattingContextRoot()); |
| } |
| |
| NGFragmentItem::NGFragmentItem( |
| const NGInlineItem& inline_item, |
| scoped_refptr<const ShapeResultView> shape_result, |
| const NGTextOffset& text_offset, |
| const PhysicalSize& size, |
| bool is_hidden_for_paint) |
| : layout_object_(inline_item.GetLayoutObject()), |
| text_({std::move(shape_result), text_offset}), |
| rect_({PhysicalOffset(), size}), |
| type_(kText), |
| sub_type_(static_cast<unsigned>(inline_item.TextType())), |
| style_variant_(static_cast<unsigned>(inline_item.StyleVariant())), |
| is_hidden_for_paint_(is_hidden_for_paint), |
| text_direction_(static_cast<unsigned>(inline_item.Direction())), |
| ink_overflow_type_(NGInkOverflow::kNotSet), |
| is_dirty_(false), |
| is_last_for_node_(true) { |
| #if DCHECK_IS_ON() |
| if (text_.shape_result) { |
| DCHECK_EQ(text_.shape_result->StartIndex(), StartOffset()); |
| DCHECK_EQ(text_.shape_result->EndIndex(), EndOffset()); |
| } |
| #endif |
| DCHECK_NE(TextType(), NGTextType::kLayoutGenerated); |
| DCHECK(!IsFormattingContextRoot()); |
| } |
| |
| NGFragmentItem::NGFragmentItem( |
| const NGInlineItem& inline_item, |
| scoped_refptr<const ShapeResultView> shape_result, |
| const String& text_content, |
| const PhysicalSize& size, |
| bool is_hidden_for_paint) |
| : layout_object_(inline_item.GetLayoutObject()), |
| generated_text_({std::move(shape_result), text_content}), |
| rect_({PhysicalOffset(), size}), |
| type_(kGeneratedText), |
| sub_type_(static_cast<unsigned>(inline_item.TextType())), |
| style_variant_(static_cast<unsigned>(inline_item.StyleVariant())), |
| is_hidden_for_paint_(is_hidden_for_paint), |
| text_direction_(static_cast<unsigned>(inline_item.Direction())), |
| ink_overflow_type_(NGInkOverflow::kNotSet), |
| is_dirty_(false), |
| is_last_for_node_(true) { |
| #if DCHECK_IS_ON() |
| if (text_.shape_result) { |
| DCHECK_EQ(text_.shape_result->StartIndex(), StartOffset()); |
| DCHECK_EQ(text_.shape_result->EndIndex(), EndOffset()); |
| } |
| #endif |
| DCHECK_EQ(TextType(), NGTextType::kLayoutGenerated); |
| DCHECK(!IsFormattingContextRoot()); |
| } |
| |
| NGFragmentItem::NGFragmentItem(const NGPhysicalLineBoxFragment& line) |
| : layout_object_(line.ContainerLayoutObject()), |
| line_({&line, /* descendants_count */ 1}), |
| rect_({PhysicalOffset(), line.Size()}), |
| type_(kLine), |
| sub_type_(static_cast<unsigned>(line.LineBoxType())), |
| style_variant_(static_cast<unsigned>(line.StyleVariant())), |
| is_hidden_for_paint_(false), |
| text_direction_(static_cast<unsigned>(line.BaseDirection())), |
| ink_overflow_type_(NGInkOverflow::kNotSet), |
| is_dirty_(false), |
| is_last_for_node_(true) { |
| DCHECK(!IsFormattingContextRoot()); |
| } |
| |
| NGFragmentItem::NGFragmentItem(const NGPhysicalBoxFragment& box, |
| TextDirection resolved_direction) |
| : layout_object_(box.GetLayoutObject()), |
| box_({&box, /* descendants_count */ 1}), |
| rect_({PhysicalOffset(), box.Size()}), |
| type_(kBox), |
| style_variant_(static_cast<unsigned>(box.StyleVariant())), |
| is_hidden_for_paint_(box.IsHiddenForPaint()), |
| text_direction_(static_cast<unsigned>(resolved_direction)), |
| ink_overflow_type_(NGInkOverflow::kNotSet), |
| is_dirty_(false), |
| is_last_for_node_(true) { |
| DCHECK_EQ(IsFormattingContextRoot(), box.IsFormattingContextRoot()); |
| } |
| |
| NGFragmentItem::NGFragmentItem(NGLogicalLineItem&& line_item, |
| WritingMode writing_mode) { |
| DCHECK(line_item.CanCreateFragmentItem()); |
| |
| if (line_item.fragment) { |
| new (this) NGFragmentItem(*line_item.fragment); |
| return; |
| } |
| |
| if (line_item.inline_item) { |
| if (UNLIKELY(line_item.text_content)) { |
| new (this) NGFragmentItem( |
| *line_item.inline_item, std::move(line_item.shape_result), |
| line_item.text_content, |
| ToPhysicalSize(line_item.MarginSize(), writing_mode), |
| line_item.is_hidden_for_paint); |
| return; |
| } |
| |
| new (this) |
| NGFragmentItem(*line_item.inline_item, |
| std::move(line_item.shape_result), line_item.text_offset, |
| ToPhysicalSize(line_item.MarginSize(), writing_mode), |
| line_item.is_hidden_for_paint); |
| return; |
| } |
| |
| if (line_item.layout_result) { |
| const NGPhysicalBoxFragment& box_fragment = |
| To<NGPhysicalBoxFragment>(line_item.layout_result->PhysicalFragment()); |
| new (this) NGFragmentItem(box_fragment, line_item.ResolvedDirection()); |
| return; |
| } |
| |
| // CanCreateFragmentItem() |
| NOTREACHED(); |
| CHECK(false); |
| } |
| |
| NGFragmentItem::NGFragmentItem(const NGFragmentItem& source) |
| : layout_object_(source.layout_object_), |
| rect_(source.rect_), |
| fragment_id_(source.fragment_id_), |
| delta_to_next_for_same_layout_object_( |
| source.delta_to_next_for_same_layout_object_), |
| type_(source.type_), |
| sub_type_(source.sub_type_), |
| style_variant_(source.style_variant_), |
| is_hidden_for_paint_(source.is_hidden_for_paint_), |
| text_direction_(source.text_direction_), |
| ink_overflow_type_(NGInkOverflow::kNotSet), |
| is_dirty_(source.is_dirty_), |
| is_last_for_node_(source.is_last_for_node_) { |
| switch (Type()) { |
| case kText: |
| new (&text_) TextItem(source.text_); |
| break; |
| case kGeneratedText: |
| new (&generated_text_) GeneratedTextItem(source.generated_text_); |
| break; |
| case kLine: |
| new (&line_) LineItem(source.line_); |
| break; |
| case kBox: |
| new (&box_) BoxItem(source.box_); |
| break; |
| } |
| |
| // Copy |ink_overflow_| only for text items, because ink overflow for other |
| // items may be chnaged even in simplified layout or when reusing lines, and |
| // that they need to be re-computed anyway. |
| if (IsText() && source.IsInkOverflowComputed()) { |
| ink_overflow_type_ = source.InkOverflowType(); |
| new (&ink_overflow_) |
| NGInkOverflow(source.InkOverflowType(), source.ink_overflow_); |
| } |
| } |
| |
| NGFragmentItem::NGFragmentItem(NGFragmentItem&& source) |
| : layout_object_(source.layout_object_), |
| rect_(source.rect_), |
| ink_overflow_(source.InkOverflowType(), std::move(source.ink_overflow_)), |
| fragment_id_(source.fragment_id_), |
| delta_to_next_for_same_layout_object_( |
| source.delta_to_next_for_same_layout_object_), |
| type_(source.type_), |
| sub_type_(source.sub_type_), |
| style_variant_(source.style_variant_), |
| is_hidden_for_paint_(source.is_hidden_for_paint_), |
| text_direction_(source.text_direction_), |
| ink_overflow_type_(source.ink_overflow_type_), |
| is_dirty_(source.is_dirty_), |
| is_last_for_node_(source.is_last_for_node_) { |
| switch (Type()) { |
| case kText: |
| new (&text_) TextItem(std::move(source.text_)); |
| break; |
| case kGeneratedText: |
| new (&generated_text_) |
| GeneratedTextItem(std::move(source.generated_text_)); |
| break; |
| case kLine: |
| new (&line_) LineItem(std::move(source.line_)); |
| break; |
| case kBox: |
| new (&box_) BoxItem(std::move(source.box_)); |
| break; |
| } |
| } |
| |
| NGFragmentItem::~NGFragmentItem() { |
| switch (Type()) { |
| case kText: |
| text_.~TextItem(); |
| break; |
| case kGeneratedText: |
| generated_text_.~GeneratedTextItem(); |
| break; |
| case kLine: |
| line_.~LineItem(); |
| break; |
| case kBox: |
| box_.~BoxItem(); |
| break; |
| } |
| ink_overflow_.Reset(InkOverflowType()); |
| } |
| |
| bool NGFragmentItem::IsInlineBox() const { |
| if (Type() == kBox) { |
| if (const NGPhysicalBoxFragment* box = BoxFragment()) |
| return box->IsInlineBox(); |
| NOTREACHED(); |
| } |
| return false; |
| } |
| |
| bool NGFragmentItem::IsAtomicInline() const { |
| if (Type() != kBox) |
| return false; |
| if (const NGPhysicalBoxFragment* box = BoxFragment()) |
| return box->IsAtomicInline(); |
| return false; |
| } |
| |
| bool NGFragmentItem::IsFloating() const { |
| if (const NGPhysicalBoxFragment* box = BoxFragment()) |
| return box->IsFloating(); |
| return false; |
| } |
| |
| bool NGFragmentItem::IsEmptyLineBox() const { |
| return LineBoxType() == NGLineBoxType::kEmptyLineBox; |
| } |
| |
| bool NGFragmentItem::IsGeneratedText() const { |
| if (Type() == kGeneratedText) { |
| DCHECK_EQ(TextType(), NGTextType::kLayoutGenerated); |
| return true; |
| } |
| DCHECK_NE(TextType(), NGTextType::kLayoutGenerated); |
| if (Type() == kText) |
| return GetLayoutObject()->IsStyleGenerated(); |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool NGFragmentItem::IsListMarker() const { |
| return layout_object_ && layout_object_->IsLayoutNGOutsideListMarker(); |
| } |
| |
| bool NGFragmentItem::HasOverflowClip() const { |
| if (const NGPhysicalBoxFragment* fragment = BoxFragment()) |
| return fragment->HasOverflowClip(); |
| return false; |
| } |
| |
| bool NGFragmentItem::HasSelfPaintingLayer() const { |
| if (const NGPhysicalBoxFragment* fragment = BoxFragment()) |
| return fragment->HasSelfPaintingLayer(); |
| return false; |
| } |
| |
| void NGFragmentItem::LayoutObjectWillBeDestroyed() const { |
| const_cast<NGFragmentItem*>(this)->layout_object_ = nullptr; |
| if (const NGPhysicalBoxFragment* fragment = BoxFragment()) |
| fragment->LayoutObjectWillBeDestroyed(); |
| } |
| |
| void NGFragmentItem::LayoutObjectWillBeMoved() const { |
| // When |Layoutobject| is moved out from the current IFC, we should not clear |
| // the association with it in |ClearAssociatedFragments|, because the |
| // |LayoutObject| may be moved to a different IFC and is already laid out |
| // before clearing this IFC. This happens e.g., when split inlines moves |
| // inline children into a child anonymous block. |
| const_cast<NGFragmentItem*>(this)->layout_object_ = nullptr; |
| } |
| |
| inline const LayoutBox* NGFragmentItem::InkOverflowOwnerBox() const { |
| if (Type() == kBox) |
| return ToLayoutBoxOrNull(GetLayoutObject()); |
| return nullptr; |
| } |
| |
| inline LayoutBox* NGFragmentItem::MutableInkOverflowOwnerBox() { |
| if (Type() == kBox) |
| return ToLayoutBoxOrNull(const_cast<LayoutObject*>(layout_object_)); |
| return nullptr; |
| } |
| |
| PhysicalRect NGFragmentItem::SelfInkOverflow() const { |
| if (const LayoutBox* box = InkOverflowOwnerBox()) |
| return box->PhysicalSelfVisualOverflowRect(); |
| if (!HasInkOverflow()) |
| return LocalRect(); |
| return ink_overflow_.Self(InkOverflowType(), Size()); |
| } |
| |
| PhysicalRect NGFragmentItem::InkOverflow() const { |
| if (const LayoutBox* box = InkOverflowOwnerBox()) |
| return box->PhysicalVisualOverflowRect(); |
| if (!HasInkOverflow()) |
| return LocalRect(); |
| if (!IsContainer() || HasOverflowClip()) |
| return ink_overflow_.Self(InkOverflowType(), Size()); |
| return ink_overflow_.SelfAndContents(InkOverflowType(), Size()); |
| } |
| |
| const ShapeResultView* NGFragmentItem::TextShapeResult() const { |
| if (Type() == kText) |
| return text_.shape_result.get(); |
| if (Type() == kGeneratedText) |
| return generated_text_.shape_result.get(); |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| NGTextOffset NGFragmentItem::TextOffset() const { |
| if (Type() == kText) |
| return text_.text_offset; |
| if (Type() == kGeneratedText) |
| return {0, generated_text_.text.length()}; |
| NOTREACHED(); |
| return {}; |
| } |
| |
| StringView NGFragmentItem::Text(const NGFragmentItems& items) const { |
| if (Type() == kText) { |
| return StringView(items.Text(UsesFirstLineStyle()), text_.text_offset.start, |
| text_.text_offset.Length()); |
| } |
| if (Type() == kGeneratedText) |
| return GeneratedText(); |
| NOTREACHED(); |
| return StringView(); |
| } |
| |
| NGTextFragmentPaintInfo NGFragmentItem::TextPaintInfo( |
| const NGFragmentItems& items) const { |
| if (Type() == kText) { |
| return {items.Text(UsesFirstLineStyle()), text_.text_offset.start, |
| text_.text_offset.end, text_.shape_result.get()}; |
| } |
| if (Type() == kGeneratedText) { |
| return {generated_text_.text, 0, generated_text_.text.length(), |
| generated_text_.shape_result.get()}; |
| } |
| NOTREACHED(); |
| return {}; |
| } |
| |
| TextDirection NGFragmentItem::BaseDirection() const { |
| DCHECK_EQ(Type(), kLine); |
| return static_cast<TextDirection>(text_direction_); |
| } |
| |
| TextDirection NGFragmentItem::ResolvedDirection() const { |
| DCHECK(Type() == kText || Type() == kGeneratedText || IsAtomicInline()); |
| return static_cast<TextDirection>(text_direction_); |
| } |
| |
| String NGFragmentItem::ToString() const { |
| // TODO(yosin): Once |NGPaintFragment| is removed, we should get rid of |
| // following if-statements. |
| // For ease of rebasing, we use same |DebugName()| as |NGPaintFrgment|. |
| if (Type() == NGFragmentItem::kBox) { |
| StringBuilder name; |
| name.Append("NGPhysicalBoxFragment "); |
| name.Append(layout_object_->DebugName()); |
| return name.ToString(); |
| } |
| if (Type() == NGFragmentItem::kText) { |
| StringBuilder name; |
| name.Append("NGPhysicalTextFragment '"); |
| const NGFragmentItems* fragment_items = nullptr; |
| if (const LayoutBlockFlow* block_flow = |
| layout_object_->FragmentItemsContainer()) { |
| for (unsigned i = 0; i < block_flow->PhysicalFragmentCount(); ++i) { |
| const NGPhysicalBoxFragment* containing_fragment = |
| block_flow->GetPhysicalFragment(i); |
| fragment_items = containing_fragment->Items(); |
| if (fragment_items) |
| break; |
| } |
| } |
| if (fragment_items) |
| name.Append(Text(*fragment_items)); |
| else |
| name.Append("(container not found)"); |
| name.Append('\''); |
| return name.ToString(); |
| } |
| if (Type() == NGFragmentItem::kLine) |
| return "NGPhysicalLineBoxFragment"; |
| return "NGFragmentItem"; |
| } |
| |
| PhysicalRect NGFragmentItem::LocalVisualRectFor( |
| const LayoutObject& layout_object) { |
| DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); |
| DCHECK(layout_object.IsInLayoutNGInlineFormattingContext()); |
| |
| PhysicalRect visual_rect; |
| NGInlineCursor cursor; |
| for (cursor.MoveTo(layout_object); cursor; |
| cursor.MoveToNextForSameLayoutObject()) { |
| DCHECK(cursor.Current().Item()); |
| const NGFragmentItem& item = *cursor.Current().Item(); |
| if (UNLIKELY(item.IsHiddenForPaint())) |
| continue; |
| PhysicalRect child_visual_rect = item.SelfInkOverflow(); |
| child_visual_rect.offset += item.OffsetInContainerBlock(); |
| visual_rect.Unite(child_visual_rect); |
| } |
| return visual_rect; |
| } |
| |
| PhysicalRect NGFragmentItem::RecalcInkOverflowForCursor( |
| NGInlineCursor* cursor) { |
| DCHECK(cursor); |
| DCHECK(!cursor->Current() || cursor->IsAtFirst()); |
| PhysicalRect contents_ink_overflow; |
| while (*cursor) { |
| const NGFragmentItem* item = cursor->CurrentItem(); |
| DCHECK(item); |
| if (UNLIKELY(item->IsLayoutObjectDestroyedOrMoved())) { |
| // TODO(crbug.com/1099613): This should not happen, as long as it is |
| // layout-clean. It looks like there are cases where the layout is dirty. |
| NOTREACHED(); |
| cursor->MoveToNextSkippingChildren(); |
| continue; |
| } |
| |
| PhysicalRect child_rect; |
| item->GetMutableForPainting().RecalcInkOverflow(cursor, &child_rect); |
| if (item->HasSelfPaintingLayer()) |
| continue; |
| if (!child_rect.IsEmpty()) { |
| child_rect.offset += item->OffsetInContainerBlock(); |
| contents_ink_overflow.Unite(child_rect); |
| } |
| } |
| return contents_ink_overflow; |
| } |
| |
| void NGFragmentItem::RecalcInkOverflow( |
| NGInlineCursor* cursor, |
| PhysicalRect* self_and_contents_rect_out) { |
| DCHECK_EQ(this, cursor->CurrentItem()); |
| |
| if (UNLIKELY(IsLayoutObjectDestroyedOrMoved())) { |
| // TODO(crbug.com/1099613): This should not happen, as long as it is really |
| // layout-clean. It looks like there are cases where the layout is dirty. |
| NOTREACHED(); |
| cursor->MoveToNextSkippingChildren(); |
| return; |
| } |
| |
| if (IsText()) { |
| cursor->MoveToNext(); |
| |
| // Re-computing text item is not necessary, because all changes that needs |
| // to re-compute ink overflow invalidate layout. |
| if (IsInkOverflowComputed()) { |
| *self_and_contents_rect_out = SelfInkOverflow(); |
| return; |
| } |
| |
| NGTextFragmentPaintInfo paint_info = TextPaintInfo(cursor->Items()); |
| if (paint_info.shape_result) { |
| ink_overflow_type_ = ink_overflow_.SetTextInkOverflow( |
| InkOverflowType(), paint_info, Style(), Size(), |
| self_and_contents_rect_out); |
| return; |
| } |
| |
| ink_overflow_type_ = ink_overflow_.Reset(InkOverflowType()); |
| *self_and_contents_rect_out = LocalRect(); |
| return; |
| } |
| |
| // If this item has an owner |LayoutBox|, let it compute. It will call back NG |
| // to compute and store the result to |LayoutBox|. Pre-paint requires ink |
| // overflow to be stored in |LayoutBox|. |
| if (LayoutBox* owner_box = MutableInkOverflowOwnerBox()) { |
| DCHECK(!HasChildren()); |
| cursor->MoveToNextSkippingChildren(); |
| owner_box->RecalcNormalFlowChildVisualOverflowIfNeeded(); |
| *self_and_contents_rect_out = owner_box->PhysicalVisualOverflowRect(); |
| return; |
| } |
| |
| // Re-compute descendants, then compute the contents ink overflow from them. |
| NGInlineCursor descendants_cursor = cursor->CursorForDescendants(); |
| cursor->MoveToNextSkippingChildren(); |
| PhysicalRect contents_rect = RecalcInkOverflowForCursor(&descendants_cursor); |
| |
| // |contents_rect| is relative to the inline formatting context. Make it |
| // relative to |this|. |
| contents_rect.offset -= OffsetInContainerBlock(); |
| |
| if (Type() == kLine) { |
| // Line boxes don't have self overflow. Compute content overflow only. |
| *self_and_contents_rect_out = contents_rect; |
| ink_overflow_type_ = |
| ink_overflow_.SetContents(InkOverflowType(), contents_rect, Size()); |
| return; |
| } |
| |
| if (const NGPhysicalBoxFragment* box_fragment = BoxFragment()) { |
| DCHECK(box_fragment->IsInlineBox()); |
| // Compute the self ink overflow. |
| PhysicalRect self_rect = box_fragment->ComputeSelfInkOverflow(); |
| *self_and_contents_rect_out = UnionRect(self_rect, contents_rect); |
| ink_overflow_type_ = |
| ink_overflow_.Set(InkOverflowType(), self_rect, contents_rect, Size()); |
| return; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| void NGFragmentItem::SetDeltaToNextForSameLayoutObject(wtf_size_t delta) const { |
| DCHECK_NE(Type(), kLine); |
| delta_to_next_for_same_layout_object_ = delta; |
| } |
| |
| PositionWithAffinity NGFragmentItem::PositionForPointInText( |
| const PhysicalOffset& point, |
| const NGInlineCursor& cursor) const { |
| DCHECK_EQ(Type(), kText); |
| DCHECK_EQ(cursor.CurrentItem(), this); |
| if (IsGeneratedText()) |
| return PositionWithAffinity(); |
| const unsigned text_offset = TextOffsetForPoint(point, cursor.Items()); |
| const NGCaretPosition unadjusted_position{ |
| cursor, NGCaretPositionType::kAtTextOffset, text_offset}; |
| if (RuntimeEnabledFeatures::BidiCaretAffinityEnabled()) |
| return unadjusted_position.ToPositionInDOMTreeWithAffinity(); |
| if (text_offset > StartOffset() && text_offset < EndOffset()) |
| return unadjusted_position.ToPositionInDOMTreeWithAffinity(); |
| return BidiAdjustment::AdjustForHitTest(unadjusted_position) |
| .ToPositionInDOMTreeWithAffinity(); |
| } |
| |
| unsigned NGFragmentItem::TextOffsetForPoint( |
| const PhysicalOffset& point, |
| const NGFragmentItems& items) const { |
| DCHECK_EQ(Type(), kText); |
| const ComputedStyle& style = Style(); |
| const LayoutUnit& point_in_line_direction = |
| style.IsHorizontalWritingMode() ? point.left : point.top; |
| if (const ShapeResultView* shape_result = TextShapeResult()) { |
| // TODO(layout-dev): Move caret logic out of ShapeResult into separate |
| // support class for code health and to avoid this copy. |
| return shape_result->CreateShapeResult()->CaretOffsetForHitTest( |
| point_in_line_direction.ToFloat(), Text(items), BreakGlyphs) + |
| StartOffset(); |
| } |
| |
| // Flow control fragments such as forced line break, tabulation, soft-wrap |
| // opportunities, etc. do not have ShapeResult. |
| DCHECK(IsFlowControl()); |
| |
| // Zero-inline-size objects such as newline always return the start offset. |
| LogicalSize size = Size().ConvertToLogical(style.GetWritingMode()); |
| if (!size.inline_size) |
| return StartOffset(); |
| |
| // Sized objects such as tabulation returns the next offset if the given point |
| // is on the right half. |
| LayoutUnit inline_offset = IsLtr(ResolvedDirection()) |
| ? point_in_line_direction |
| : size.inline_size - point_in_line_direction; |
| DCHECK_EQ(1u, TextLength()); |
| return inline_offset <= size.inline_size / 2 ? StartOffset() : EndOffset(); |
| } |
| |
| std::ostream& operator<<(std::ostream& ostream, const NGFragmentItem& item) { |
| ostream << "{"; |
| switch (item.Type()) { |
| case NGFragmentItem::kText: |
| ostream << "Text " << item.StartOffset() << "-" << item.EndOffset() << " " |
| << (IsLtr(item.ResolvedDirection()) ? "LTR" : "RTL"); |
| break; |
| case NGFragmentItem::kGeneratedText: |
| ostream << "GeneratedText \"" << item.GeneratedText() << "\""; |
| break; |
| case NGFragmentItem::kLine: |
| ostream << "Line #descendants=" << item.DescendantsCount() << " " |
| << (IsLtr(item.BaseDirection()) ? "LTR" : "RTL"); |
| break; |
| case NGFragmentItem::kBox: |
| ostream << "Box #descendants=" << item.DescendantsCount(); |
| if (item.IsAtomicInline()) { |
| ostream << " AtomicInline" |
| << (IsLtr(item.ResolvedDirection()) ? "LTR" : "RTL"); |
| } |
| break; |
| } |
| ostream << " "; |
| switch (item.StyleVariant()) { |
| case NGStyleVariant::kStandard: |
| ostream << "Standard"; |
| break; |
| case NGStyleVariant::kFirstLine: |
| ostream << "FirstLine"; |
| break; |
| case NGStyleVariant::kEllipsis: |
| ostream << "Ellipsis"; |
| break; |
| } |
| return ostream << "}"; |
| } |
| |
| std::ostream& operator<<(std::ostream& ostream, const NGFragmentItem* item) { |
| if (!item) |
| return ostream << "<null>"; |
| return ostream << *item; |
| } |
| |
| } // namespace blink |