blob: 795317f93782b115e24be6b25b5c0d3a882cf4dc [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_FRAGMENT_ITEM_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_FRAGMENT_ITEM_H_
#include "base/memory/scoped_refptr.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_type.h"
#include "third_party/blink/renderer/core/layout/ng/ng_ink_overflow.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
#include "third_party/blink/renderer/platform/graphics/paint/display_item_client.h"
#include "third_party/blink/renderer/platform/wtf/ref_counted.h"
namespace blink {
class NGFragmentItems;
class NGInlineBreakToken;
struct NGTextFragmentPaintInfo;
struct NGLogicalLineItem;
// This class represents a text run or a box in an inline formatting context.
//
// This class consumes less memory than a full fragment, and can be stored in a
// flat list (NGFragmentItems) for easier and faster traversal.
class CORE_EXPORT NGFragmentItem {
public:
// Represents regular text that exists in the DOM.
struct TextItem {
scoped_refptr<const ShapeResultView> shape_result;
// TODO(kojii): |text_offset| should match to the offset in |shape_result|.
// Consider if we should remove them, or if keeping them is easier.
const NGTextOffset text_offset;
};
// Represents text generated by the layout engine, e.g., hyphen or ellipsis.
struct GeneratedTextItem {
scoped_refptr<const ShapeResultView> shape_result;
String text;
};
// A start marker of a line box.
struct LineItem {
scoped_refptr<const NGPhysicalLineBoxFragment> line_box_fragment;
wtf_size_t descendants_count;
};
// Represents a box fragment appeared in a line. This includes inline boxes
// (e.g., <span>text</span>) and atomic inlines.
struct BoxItem {
// This copy constructor looks up the "post-layout" fragment.
BoxItem(const BoxItem&);
BoxItem(scoped_refptr<const NGPhysicalBoxFragment>,
wtf_size_t descendants_count);
// If this item is an inline box, its children are stored as following
// items. |descendants_count_| has the number of such items.
//
// If this item is a root of another IFC/BFC, children are stored normally,
// as children of |box_fragment|.
const NGPhysicalBoxFragment* PostLayout() const;
scoped_refptr<const NGPhysicalBoxFragment> box_fragment;
wtf_size_t descendants_count;
};
enum ItemType { kText, kGeneratedText, kLine, kBox };
// Create appropriate type for |line_item|.
NGFragmentItem(NGLogicalLineItem&& line_item, WritingMode writing_mode);
// Create a box item.
NGFragmentItem(const NGPhysicalBoxFragment& box,
TextDirection resolved_direction);
// Create a line item.
explicit NGFragmentItem(const NGPhysicalLineBoxFragment& line);
// The copy/move constructors.
NGFragmentItem(const NGFragmentItem&);
NGFragmentItem(NGFragmentItem&&);
~NGFragmentItem();
ItemType Type() const { return static_cast<ItemType>(type_); }
bool IsText() const { return Type() == kText || Type() == kGeneratedText; }
bool IsContainer() const { return Type() == kBox || Type() == kLine; }
bool IsInlineBox() const;
bool IsAtomicInline() const;
bool IsFloating() const;
bool IsEmptyLineBox() const;
bool IsHiddenForPaint() const { return is_hidden_for_paint_; }
bool IsListMarker() const;
// A sequence number of fragments generated from a |LayoutObject|.
// For line boxes, please see |kInitialLineFragmentId|.
wtf_size_t FragmentId() const {
DCHECK_NE(Type(), kLine);
return fragment_id_;
}
void SetFragmentId(wtf_size_t id) const {
DCHECK_NE(Type(), kLine);
fragment_id_ = id;
}
// The initial framgent_id for line boxes.
// TODO(kojii): This is to avoid conflict with multicol because line boxes use
// its |LayoutBlockFlow| as their |DisplayItemClient|, but multicol also uses
// fragment id for |LayoutBlockFlow| today. The plan is to make |FragmentData|
// a |DisplayItemClient| instead.
// TODO(kojii): The fragment id for line boxes must be unique across NG block
// fragmentation. This is not implemented yet.
static constexpr wtf_size_t kInitialLineFragmentId = 0x80000000;
// Return true if this is the first fragment generated from a node.
bool IsFirstForNode() const { return !FragmentId(); }
// Return true if this is the last fragment generated from a node.
bool IsLastForNode() const {
DCHECK(Type() != kLine);
return is_last_for_node_;
}
void SetIsLastForNode(bool is_last) const { is_last_for_node_ = is_last; }
NGStyleVariant StyleVariant() const {
return static_cast<NGStyleVariant>(style_variant_);
}
bool UsesFirstLineStyle() const {
return StyleVariant() == NGStyleVariant::kFirstLine;
}
// Returns the style for this fragment.
//
// For a line box, this returns the style of the containing block. This mostly
// represents the style for the line box, except 1) |style.Direction()| maybe
// incorrect, use |BaseDirection()| instead, and 2) margin/border/padding,
// background etc. do not apply to the line box.
const ComputedStyle& Style() const {
return layout_object_->EffectiveStyle(StyleVariant());
}
const LayoutObject* GetLayoutObject() const { return layout_object_; }
LayoutObject* GetMutableLayoutObject() const {
return const_cast<LayoutObject*>(layout_object_);
}
bool IsLayoutObjectDestroyedOrMoved() const { return !layout_object_; }
void LayoutObjectWillBeDestroyed() const;
void LayoutObjectWillBeMoved() const;
Node* GetNode() const { return layout_object_->GetNode(); }
Node* NodeForHitTest() const { return layout_object_->NodeForHitTest(); }
// Use |LayoutObject|+|FragmentId()| for |DisplayItem::Id|.
const DisplayItemClient* GetDisplayItemClient() const {
return GetLayoutObject();
}
wtf_size_t DeltaToNextForSameLayoutObject() const {
return delta_to_next_for_same_layout_object_;
}
void SetDeltaToNextForSameLayoutObject(wtf_size_t delta) const;
const PhysicalRect& RectInContainerFragment() const { return rect_; }
const PhysicalOffset& OffsetInContainerFragment() const {
return rect_.offset;
}
const PhysicalSize& Size() const { return rect_.size; }
PhysicalRect LocalRect() const { return {PhysicalOffset(), Size()}; }
void SetOffset(const PhysicalOffset& offset) { rect_.offset = offset; }
PhysicalRect InkOverflow() const;
PhysicalRect SelfInkOverflow() const;
// Count of following items that are descendants of this item in the box tree,
// including this item. 1 means this is a box (box or line box) without
// descendants. 0 if this item type cannot have children.
wtf_size_t DescendantsCount() const {
if (Type() == kBox)
return box_.descendants_count;
if (Type() == kLine)
return line_.descendants_count;
DCHECK(!IsContainer());
return 0;
}
bool HasChildren() const { return DescendantsCount() > 1; }
void SetDescendantsCount(wtf_size_t count) {
if (Type() == kBox) {
box_.descendants_count = count;
return;
}
if (Type() == kLine) {
line_.descendants_count = count;
return;
}
NOTREACHED();
}
// Returns |NGPhysicalBoxFragment| if one is associated with this item.
const NGPhysicalBoxFragment* BoxFragment() const {
if (Type() == kBox)
return box_.box_fragment.get();
return nullptr;
}
const NGPhysicalBoxFragment* PostLayoutBoxFragment() const {
if (Type() == kBox)
return box_.PostLayout();
return nullptr;
}
bool HasNonVisibleOverflow() const;
bool IsScrollContainer() const;
bool HasSelfPaintingLayer() const;
// TODO(kojii): Avoid using this function in outside of this class as much as
// possible, because |NGPhysicalLineBoxFragment| is likely to be removed. Add
// functions to access data in |NGPhysicalLineBoxFragment| rather than using
// this function. See |InlineBreakToken()| for example.
const NGPhysicalLineBoxFragment* LineBoxFragment() const {
if (Type() == kLine)
return line_.line_box_fragment.get();
return nullptr;
}
// Returns |NGInlineBreakToken| associated with this line, for line items.
// Calling this function for other types is not valid.
const NGInlineBreakToken* InlineBreakToken() const {
if (const NGPhysicalLineBoxFragment* line_box = LineBoxFragment())
return To<NGInlineBreakToken>(line_box->BreakToken());
NOTREACHED();
return nullptr;
}
using NGLineBoxType = NGPhysicalLineBoxFragment::NGLineBoxType;
NGLineBoxType LineBoxType() const {
if (Type() == kLine)
return static_cast<NGLineBoxType>(sub_type_);
NOTREACHED() << this;
return NGLineBoxType::kNormalLineBox;
}
static PhysicalRect LocalVisualRectFor(const LayoutObject& layout_object);
// Re-compute the ink overflow for the |cursor| until its end.
static PhysicalRect RecalcInkOverflowForCursor(NGInlineCursor* cursor);
// Painters can use const methods only, except for these explicitly declared
// methods.
class MutableForPainting {
STACK_ALLOCATED();
public:
void InvalidateInkOverflow() { return item_.InvalidateInkOverflow(); }
void RecalcInkOverflow(const NGInlineCursor& cursor,
PhysicalRect* self_and_contents_rect_out) {
return item_.RecalcInkOverflow(cursor, self_and_contents_rect_out);
}
private:
friend class NGFragmentItem;
MutableForPainting(const NGFragmentItem& item)
: item_(const_cast<NGFragmentItem&>(item)) {}
NGFragmentItem& item_;
};
MutableForPainting GetMutableForPainting() const {
return MutableForPainting(*this);
}
bool IsHorizontal() const {
return IsHorizontalWritingMode(GetWritingMode());
}
WritingMode GetWritingMode() const {
return Style().GetWritingMode();
}
// Functions for |TextItem| and |GeneratedTextItem|
NGTextType TextType() const {
if (Type() == kText)
return static_cast<NGTextType>(sub_type_);
if (Type() == kGeneratedText)
return NGTextType::kLayoutGenerated;
NOTREACHED() << this;
return NGTextType::kNormal;
}
// True if this is a forced line break.
bool IsLineBreak() const {
return TextType() == NGTextType::kForcedLineBreak;
}
// True if this is not for painting; i.e., a forced line break, a tabulation,
// or a soft-wrap opportunity.
bool IsFlowControl() const {
return IsLineBreak() || TextType() == NGTextType::kFlowControl;
}
// True if this is an ellpisis generated by `text-overflow: ellipsis`.
bool IsEllipsis() const {
return StyleVariant() == NGStyleVariant::kEllipsis;
}
// Returns true if the text is generated (from, e.g., list marker,
// pseudo-element, ...) instead of from a DOM text node.
// * CSS content kText
// * ellipsis kGeneratedText
// * first-letter-part kText
// * list marker kGeneratedText
// * soft hyphen kGeneratedText
// TODO(yosin): When we implement |kGeneratedText|, we rename this function
// to avoid confliction with |kGeneratedText|.
bool IsGeneratedText() const;
bool IsSymbolMarker() const {
return TextType() == NGTextType::kSymbolMarker;
}
bool IsFormattingContextRoot() const {
return BoxFragment() && !IsInlineBox();
}
const ShapeResultView* TextShapeResult() const;
NGTextOffset TextOffset() const;
unsigned StartOffset() const { return TextOffset().start; }
unsigned EndOffset() const { return TextOffset().end; }
unsigned TextLength() const { return TextOffset().Length(); }
StringView Text(const NGFragmentItems& items) const;
String GeneratedText() const {
DCHECK_EQ(Type(), kGeneratedText);
return generated_text_.text;
}
NGTextFragmentPaintInfo TextPaintInfo(const NGFragmentItems& items) const;
// Compute the inline position from text offset, in logical coordinate
// relative to this fragment.
LayoutUnit InlinePositionForOffset(StringView text,
unsigned offset,
LayoutUnit (*round_function)(float),
AdjustMidCluster) const;
LayoutUnit InlinePositionForOffset(StringView text, unsigned offset) const;
// Compute line-relative coordinates for given offsets, this is not
// flow-relative:
// https://drafts.csswg.org/css-writing-modes-3/#line-directions
std::pair<LayoutUnit, LayoutUnit> LineLeftAndRightForOffsets(
StringView text,
unsigned start_offset,
unsigned end_offset) const;
// The layout box of text in (start, end) range in local coordinate.
// Start and end offsets must be between StartOffset() and EndOffset().
PhysicalRect LocalRect(StringView text,
unsigned start_offset,
unsigned end_offset) const;
// The base direction of line. Also known as the paragraph direction. This may
// be different from the direction of the container box when first-line style
// is used, or when 'unicode-bidi: plaintext' is used.
// Note: This is valid only for |LineItem|.
TextDirection BaseDirection() const;
// Direction of this item valid for |TextItem| and |IsAtomicInline()|.
// Note: <span> doesn't have text direction.
TextDirection ResolvedDirection() const;
// Converts the given point, relative to the fragment itself, into a position
// in DOM tree.
PositionWithAffinity PositionForPointInText(
const PhysicalOffset& point,
const NGInlineCursor& cursor) const;
PositionWithAffinity PositionForPointInText(
unsigned text_offset,
const NGInlineCursor& cursor) const;
unsigned TextOffsetForPoint(const PhysicalOffset& point,
const NGFragmentItems& items) const;
// Whether this item was marked dirty for reuse or not.
bool IsDirty() const { return is_dirty_; }
void SetDirty() const { is_dirty_ = true; }
// Returns true if this item is reusable.
bool CanReuse() const;
const NGFragmentItem* operator->() const { return this; }
// Get a description of |this| for the debug purposes.
String ToString() const;
private:
FRIEND_TEST_ALL_PREFIXES(NGFragmentItemTest, CopyMove);
FRIEND_TEST_ALL_PREFIXES(NGFragmentItemTest, SelfPaintingInlineBox);
FRIEND_TEST_ALL_PREFIXES(StyleChangeTest, NeedsCollectInlinesOnStyle);
// Create a text item.
NGFragmentItem(const NGInlineItem& inline_item,
scoped_refptr<const ShapeResultView> shape_result,
const NGTextOffset& text_offset,
const PhysicalSize& size,
bool is_hidden_for_paint);
// Create a generated text item.
NGFragmentItem(const NGInlineItem& inline_item,
scoped_refptr<const ShapeResultView> shape_result,
const String& text_content,
const PhysicalSize& size,
bool is_hidden_for_paint);
NGFragmentItem(const LayoutObject& layout_object,
NGTextType text_type,
NGStyleVariant style_variant,
TextDirection direction,
scoped_refptr<const ShapeResultView> shape_result,
const String& text_content,
const PhysicalSize& size,
bool is_hidden_for_paint);
NGInkOverflow::Type InkOverflowType() const {
return static_cast<NGInkOverflow::Type>(ink_overflow_type_);
}
bool IsInkOverflowComputed() const {
return InkOverflowType() != NGInkOverflow::kNotSet;
}
bool HasInkOverflow() const {
return InkOverflowType() != NGInkOverflow::kNone;
}
const LayoutBox* InkOverflowOwnerBox() const;
LayoutBox* MutableInkOverflowOwnerBox();
void InvalidateInkOverflow();
// Re-compute the ink overflow for this item. |cursor| should be at |this|.
void RecalcInkOverflow(const NGInlineCursor& cursor,
PhysicalRect* self_and_contents_rect_out);
const LayoutObject* layout_object_;
// TODO(kojii): We can make them sub-classes if we need to make the vector of
// pointers. Sub-classing from DisplayItemClient prohibits copying and that we
// cannot create a vector of this class.
union {
TextItem text_;
GeneratedTextItem generated_text_;
LineItem line_;
BoxItem box_;
};
PhysicalRect rect_;
NGInkOverflow ink_overflow_;
mutable wtf_size_t fragment_id_ = 0;
// Item index delta to the next item for the same |LayoutObject|.
mutable wtf_size_t delta_to_next_for_same_layout_object_ = 0;
// Note: We should not add |bidi_level_| because it is used only for layout.
unsigned type_ : 2; // ItemType
unsigned sub_type_ : 3; // NGTextType or NGLineBoxType
unsigned style_variant_ : 2; // NGStyleVariant
unsigned is_hidden_for_paint_ : 1;
// Note: For |TextItem| and |GeneratedTextItem|, |text_direction_| equals to
// |ShapeResult::Direction()|.
unsigned text_direction_ : 1; // TextDirection.
unsigned ink_overflow_type_ : 3; // NGInkOverflow::Type
mutable unsigned is_dirty_ : 1;
mutable unsigned is_last_for_node_ : 1;
};
inline bool NGFragmentItem::CanReuse() const {
DCHECK_NE(Type(), kLine);
if (IsDirty())
return false;
if (const LayoutObject* layout_object = GetLayoutObject())
return !layout_object->SelfNeedsLayout();
return false;
}
CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGFragmentItem*);
CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGFragmentItem&);
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_FRAGMENT_ITEM_H_