blob: e7c3bd24a61632aefbe5f61eb7f4bc70f663ea9c [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_INLINE_CURSOR_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_CURSOR_H_
#include <unicode/ubidi.h>
#include "base/containers/span.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/inline/ng_fragment_items.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h"
#include "third_party/blink/renderer/platform/text/text_direction.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
#include "third_party/blink/renderer/platform/wtf/text/string_view.h"
namespace blink {
class ComputedStyle;
class DisplayItemClient;
class LayoutBlockFlow;
class LayoutInline;
class LayoutObject;
class LayoutUnit;
class NGFragmentItem;
class NGFragmentItems;
class NGInlineBackwardCursor;
class NGInlineBreakToken;
class NGInlineCursor;
class NGPaintFragment;
class NGPhysicalBoxFragment;
class Node;
class ShapeResultView;
enum class NGStyleVariant;
struct PhysicalOffset;
struct PhysicalRect;
struct PhysicalSize;
// Represents a position of |NGInlineCursor|. This class:
// 1. Provides properties for the current position.
// 2. Allows to save |Current()|, and can move back later. Moving to |Position|
// is faster than moving to |NGFragmentItem|.
class CORE_EXPORT NGInlineCursorPosition {
public:
using ItemsSpan = NGFragmentItems::Span;
const NGPaintFragment* PaintFragment() const { return paint_fragment_; }
const NGFragmentItem* Item() const { return item_; }
const NGFragmentItem* operator->() const { return item_; }
const NGFragmentItem& operator*() const { return *item_; }
operator bool() const { return paint_fragment_ || item_; }
bool operator==(const NGInlineCursorPosition& other) const {
return paint_fragment_ == other.paint_fragment_ && item_ == other.item_;
}
bool operator!=(const NGInlineCursorPosition& other) const {
return !operator==(other);
}
// True if the current position is a text. It is error to call at end.
bool IsText() const;
// True if the current position is a generatd text. It is error to call at
// end. This includes both style-generated (e.g., `content` property, see
// |IsStyleGenerated()|) and layout-generated (hyphens and ellipsis, see
// |IsLayoutGeneratedText()|.)
bool IsGeneratedText() const;
// True if fragment is layout-generated (hyphens and ellipsis.)
bool IsLayoutGeneratedText() const;
// True if the current position is a line break. It is error to call at end.
bool IsLineBreak() const;
// True if the current position is an ellipsis. It is error to call at end.
bool IsEllipsis() const;
// True if the current position is a line box. It is error to call at end.
bool IsLineBox() const;
// True if the current position is an empty line box. It is error to call
// other then line box.
bool IsEmptyLineBox() const;
// True if the current position is an inline box. It is error to call at end.
bool IsInlineBox() const;
// True if the current position is an atomic inline. It is error to call at
// end.
bool IsAtomicInline() const;
// True if the current position is a list marker.
bool IsListMarker() const;
// True if the current position is hidden for paint. It is error to call at
// end.
bool IsHiddenForPaint() const;
// |ComputedStyle| and related functions.
NGStyleVariant StyleVariant() const;
bool UsesFirstLineStyle() const;
const ComputedStyle& Style() const;
// Functions to get corresponding objects for this position.
const NGPhysicalBoxFragment* BoxFragment() const;
const LayoutObject* GetLayoutObject() const;
LayoutObject* GetMutableLayoutObject() const;
const Node* GetNode() const;
const DisplayItemClient* GetDisplayItemClient() const;
const DisplayItemClient* GetSelectionDisplayItemClient() const;
wtf_size_t FragmentId() const;
// True if fragment at the current position can have children.
bool CanHaveChildren() const;
// True if fragment at the current position has children.
bool HasChildren() const;
// Returns break token for line box. It is error to call other than line box.
const NGInlineBreakToken* InlineBreakToken() const;
// The offset relative to the root of the inline formatting context.
const PhysicalRect RectInContainerBlock() const;
const PhysicalOffset OffsetInContainerBlock() const;
const PhysicalSize Size() const;
// InkOverflow of itself, including contents if they contribute to the ink
// overflow of this object (e.g. when not clipped,) in the local coordinate.
const PhysicalRect InkOverflow() const;
const PhysicalRect SelfInkOverflow() const;
// Returns start/end of offset in text content of current text fragment.
// It is error when this cursor doesn't point to text fragment.
NGTextOffset TextOffset() const;
unsigned TextStartOffset() const { return TextOffset().start; }
unsigned TextEndOffset() const { return TextOffset().end; }
// Returns text of the current position. It is error to call other than
// text.
StringView Text(const NGInlineCursor& cursor) const;
// Returns |ShapeResultView| of the current position. It is error to call
// other than text.
const ShapeResultView* TextShapeResult() const;
// Returns bidi level of current position. It is error to call other than
// text and atomic inline. It is also error to call |IsGeneratedTextType()|.
UBiDiLevel BidiLevel() const;
// Returns text direction of current text or atomic inline. It is error to
// call at other than text or atomic inline. Note: <span> doesn't have
// reserved direction.
TextDirection ResolvedDirection() const;
// Returns text direction of current line. It is error to call at other than
// line.
TextDirection BaseDirection() const;
TextDirection ResolvedOrBaseDirection() const {
return IsLineBox() ? BaseDirection() : ResolvedDirection();
}
// True if the current position is text or atomic inline box.
// Note: Because of this function is used for caret rect, hit testing, etc,
// this function returns false for hidden for paint, text overflow ellipsis,
// and line break hyphen.
bool IsInlineLeaf() const;
// True if current position has soft wrap to next line. It is error to call
// other than line.
bool HasSoftWrapToNextLine() const;
// Returns a point at the visual start/end of the line. (0, 0) is left-top of
// the line.
// Encapsulates the handling of text direction and writing mode.
PhysicalOffset LineStartPoint() const;
PhysicalOffset LineEndPoint() const;
// LogicalRect/PhysicalRect conversions
// |logical_rect| and |physical_rect| are converted with |Size()| as
// "outer size".
LogicalRect ConvertChildToLogical(const PhysicalRect& physical_rect) const;
PhysicalRect ConvertChildToPhysical(const LogicalRect& logical_rect) const;
private:
void Set(const ItemsSpan::iterator& iter) {
DCHECK(!paint_fragment_);
item_iter_ = iter;
item_ = &*iter;
}
void Clear() {
paint_fragment_ = nullptr;
item_ = nullptr;
}
const NGPaintFragment* paint_fragment_ = nullptr;
const NGFragmentItem* item_ = nullptr;
ItemsSpan::iterator item_iter_;
friend class NGInlineBackwardCursor;
friend class NGInlineCursor;
};
// This class traverses fragments in an inline formatting context.
//
// When constructed, the initial position is empty. Call |MoveToNext()| to move
// to the first fragment.
//
// TODO(kojii): |NGPaintFragment| should be gone when |NGPaintFragment| is
// deprecated and all its uses are removed.
class CORE_EXPORT NGInlineCursor {
STACK_ALLOCATED();
public:
using ItemsSpan = NGFragmentItems::Span;
explicit NGInlineCursor(const LayoutBlockFlow& block_flow);
explicit NGInlineCursor(const NGFragmentItems& items);
explicit NGInlineCursor(const NGFragmentItems& fragment_items,
ItemsSpan items);
explicit NGInlineCursor(const NGPaintFragment& root_paint_fragment);
explicit NGInlineCursor(const NGInlineBackwardCursor& backward_cursor);
NGInlineCursor(const NGInlineCursor& other) = default;
// Creates an |NGInlineCursor| without the root. Even when callers don't know
// the root of the inline formatting context, this cursor can |MoveTo()|
// specific |LayoutObject|.
NGInlineCursor() = default;
bool operator==(const NGInlineCursor& other) const;
bool operator!=(const NGInlineCursor& other) const {
return !operator==(other);
}
bool IsItemCursor() const { return fragment_items_; }
bool IsPaintFragmentCursor() const { return root_paint_fragment_; }
// True if this cursor has the root to traverse. Only the default constructor
// creates a cursor without the root.
bool HasRoot() const { return IsItemCursor() || IsPaintFragmentCursor(); }
const NGFragmentItems& Items() const {
DCHECK(fragment_items_);
return *fragment_items_;
}
// Returns the |LayoutBlockFlow| containing this cursor.
const LayoutBlockFlow* GetLayoutBlockFlow() const;
//
// Functions to query the current position.
//
const NGInlineCursorPosition& Current() const { return current_; }
// Returns true if cursor is out of fragment tree, e.g. before first fragment
// or after last fragment in tree.
bool IsNull() const { return !Current(); }
bool IsNotNull() const { return Current(); }
operator bool() const { return Current(); }
// True if |Current()| is at the first fragment. See |MoveToFirst()|.
bool IsAtFirst() const;
// Returns a new |NGInlineCursor| whose root is the current item. The returned
// cursor can traverse descendants of the current item. If the current item
// has no children, returns an empty cursor.
NGInlineCursor CursorForDescendants() const;
// If |this| is created by |CursorForDescendants()| to traverse parts of an
// inline formatting context, expand the traversable range to the containing
// |LayoutBlockFlow|. Does nothing if |this| is for an inline formatting
// context.
void ExpandRootToContainingBlock();
// True if the current position is before soft line break. It is error to call
// at end.
bool IsBeforeSoftLineBreak() const;
// |Current*| functions return an object for the current position.
const NGFragmentItem* CurrentItem() const { return Current().Item(); }
const NGPaintFragment* CurrentPaintFragment() const {
return Current().PaintFragment();
}
LayoutObject* CurrentMutableLayoutObject() const {
return Current().GetMutableLayoutObject();
}
// Returns text of the current position. It is error to call other than
// text.
StringView CurrentText() const { return Current().Text(*this); }
// The layout box of text in (start, end) range in local coordinate.
// Start and end offsets must be between |CurrentTextStartOffset()| and
// |CurrentTextEndOffset()|. It is error to call other than text.
PhysicalRect CurrentLocalRect(unsigned start_offset,
unsigned end_offset) const;
// Relative to fragment of the current position. It is error to call other
// than text.
LayoutUnit InlinePositionForOffset(unsigned offset) const;
// Converts the given point, relative to the fragment itself, into a position
// in DOM tree within the range of |this|. This variation ignores the inline
// offset, and snaps to the nearest line in the block direction.
PositionWithAffinity PositionForPointInInlineFormattingContext(
const PhysicalOffset& point,
const NGPhysicalBoxFragment& container);
// Find the |Position| in the line box |Current()| points to. This variation
// ignores the block offset, and snaps to the nearest item in inline
// direction.
PositionWithAffinity PositionForPointInInlineBox(
const PhysicalOffset& point) const;
// Returns |PositionWithAffinity| in current position at x-coordinate of
// |point_in_container| for horizontal writing mode, or y-coordinate of
// |point_in_container| for vertical writing mode.
// Note: Even if |point_in_container| is outside of an item of current
// position, this function returns boundary position of an item.
// Note: This function is used for locating caret at same x/y-coordinate as
// previous caret after line up/down.
PositionWithAffinity PositionForPointInChild(
const PhysicalOffset& point_in_container) const;
// Returns first/last position of |this| line. |this| should be line box.
PositionWithAffinity PositionForStartOfLine() const;
PositionWithAffinity PositionForEndOfLine() const;
//
// Functions to move the current position.
//
void MoveTo(const NGInlineCursorPosition& position);
// Move the current position at |fragment_item|.
void MoveTo(const NGFragmentItem& fragment_item);
// Move the current position at |cursor|. Unlinke copy constrcutr, this
// function doesn't copy root. Note: The current position in |cursor|
// should be part of |this| cursor.
void MoveTo(const NGInlineCursor& cursor);
// Move the current posint at |paint_fragment|.
void MoveTo(const NGPaintFragment& paint_fragment);
void MoveTo(const NGPaintFragment* paint_fragment);
// Move to containing line box. It is error if the current position is line.
void MoveToContainingLine();
// Move to first child of current container box. If the current position is
// at fragment without children, this cursor points nothing.
// See also |TryToMoveToFirstChild()|.
void MoveToFirstChild();
// Move to the first line.
void MoveToFirstLine();
// Move to first logical leaf of current line box. If current line box has
// no children, curosr becomes null.
void MoveToFirstLogicalLeaf();
// Move to last child of current container box. If the current position is
// at fragment without children, this cursor points nothing.
// See also |TryToMoveToFirstChild()|.
void MoveToLastChild();
// Move to the last line item. If there are no line items, the cursor becomes
// null.
void MoveToLastLine();
// Move to last logical leaf of current line box. If current line box has
// no children, curosr becomes null.
void MoveToLastLogicalLeaf();
// Move the current position to the next fragment in pre-order DFS. When
// the current position is at last fragment, this cursor points nothing.
void MoveToNext();
// Move the current position to next line. It is error to call other than line
// box.
void MoveToNextLine();
// Same as |MoveToNext| except that this skips children even if they exist.
void MoveToNextSkippingChildren();
// Move the current to next/previous inline leaf.
void MoveToNextInlineLeaf();
void MoveToNextInlineLeafIgnoringLineBreak();
void MoveToPreviousInlineLeaf();
void MoveToPreviousInlineLeafIgnoringLineBreak();
// Move the current position to next/previous inline leaf item on line.
// Note: If the current position isn't leaf item, this function moves the
// current position to leaf item then moves to next/previous leaf item. This
// behavior doesn't match |MoveTo{Next,Previous}InlineLeaf()|, but AX requires
// this. See AccessibilityLayoutTest.NextOnLine
void MoveToNextInlineLeafOnLine();
void MoveToPreviousInlineLeafOnLine();
// Move the cursor position to previous fragment in pre-order DFS.
void MoveToPrevious();
// Move the current position to previous line. It is error to call other than
// line box.
void MoveToPreviousLine();
// Returns true if the current position moves to first child.
bool TryToMoveToFirstChild();
// Returns true if the current position moves to first inline leaf child.
bool TryToMoveToFirstInlineLeafChild();
// Returns true if the current position moves to last child.
bool TryToMoveToLastChild();
//
// Moving across fragmentainers.
//
// When rooted at |LayoutBlockFlow|, |this| can move the current position
// across fragmentainers. Other root objects (e.g. |NGFragmentItems|) can
// contain only one fragmentainer that such cursors cannot move to different
// fragmentainers. See |CanMoveAcrossFragmentainer()|.
//
// However, |MoveToNext| etc. does not move the current position across
// fragmentainers. Use following functions when moving to different
// fragmentainers.
// Move to the first item of the first fragmentainer.
void MoveToFirstIncludingFragmentainer();
// Move to the next fragmentainer. Valid when |CanMoveAcrossFragmentainer|.
void MoveToNextFragmentainer();
// Same as |MoveToNext|, except this moves to the next fragmentainer if
// |Current| is at the end of a fragmentainer.
void MoveToNextIncludingFragmentainer();
//
// Functions to enumerate fragments for a |LayoutObject|.
//
// Move to first |NGFragmentItem| or |NGPaintFragment| associated to
// |layout_object|. When |layout_object| has no associated fragments, this
// cursor points nothing.
void MoveTo(const LayoutObject& layout_object);
// Same as |MoveTo|, except that this enumerates fragments for descendants
// if |layout_object| is a culled inline.
//
// Note, for a culled inline, fragments may not be in the visual order in
// the inline direction if RTL or mixed bidi for a performance reason.
void MoveToIncludingCulledInline(const LayoutObject& layout_object);
// Move the current position to next fragment on same layout object.
void MoveToNextForSameLayoutObject();
// Move the current position to the last fragment on same layout object.
void MoveToLastForSameLayoutObject();
#if DCHECK_IS_ON()
void CheckValid(const NGInlineCursorPosition& position) const;
#else
void CheckValid(const NGInlineCursorPosition&) const {}
#endif
private:
// Returns true if |this| is only for a part of an inline formatting context;
// in other words, if |this| is created by |CursorForDescendants|.
bool IsDescendantsCursor() const {
if (fragment_items_)
return !fragment_items_->Equals(items_);
if (root_paint_fragment_)
return root_paint_fragment_->Parent();
return false;
}
bool CanMoveAcrossFragmentainer() const {
return root_block_flow_ && IsItemCursor() && !IsDescendantsCursor();
}
// True if the current position is a last line in inline block. It is error
// to call at end or the current position is not line.
bool IsLastLineInInlineBlock() const;
// Index conversions for |IsDescendantsCursor()|.
wtf_size_t SpanBeginItemIndex() const;
wtf_size_t SpanIndexFromItemIndex(unsigned index) const;
// Make the current position points nothing, e.g. cursor moves over start/end
// fragment, cursor moves to first/last child to parent has no children.
void MakeNull() { current_.Clear(); }
// Move the cursor position to the first fragment in tree.
void MoveToFirst();
void SetRoot(const NGFragmentItems& items);
void SetRoot(const NGFragmentItems& fragment_items, ItemsSpan items);
void SetRoot(const NGPaintFragment& root_paint_fragment);
void SetRoot(const LayoutBlockFlow& block_flow);
bool SetRoot(const LayoutBlockFlow& block_flow, wtf_size_t fragment_index);
bool TrySetRootFragmentItems();
void MoveToItem(const ItemsSpan::iterator& iter);
void MoveToNextItem();
void MoveToNextItemSkippingChildren();
void MoveToPreviousItem();
void MoveToParentPaintFragment();
void MoveToNextPaintFragment();
void MoveToNextSiblingPaintFragment();
void MoveToNextPaintFragmentSkippingChildren();
void MoveToPreviousPaintFragment();
void MoveToPreviousSiblingPaintFragment();
void SlowMoveToFirstFor(const LayoutObject& layout_object);
void SlowMoveToNextForSameLayoutObject(const LayoutObject& layout_object);
void SlowMoveToForIfNeeded(const LayoutObject& layout_object);
// |MoveToNextForSameLayoutObject| that doesn't check |culled_inline_|.
void MoveToNextForSameLayoutObjectExceptCulledInline();
// A helper class to enumerate |LayoutObject|s that contribute to a culled
// inline.
class CulledInlineTraversal {
STACK_ALLOCATED();
public:
CulledInlineTraversal() = default;
explicit operator bool() const { return current_object_; }
void Reset() { current_object_ = nullptr; }
// Returns first/next |LayoutObject| that contribute to |layout_inline|.
const LayoutObject* MoveToFirstFor(const LayoutInline& layout_inline);
const LayoutObject* MoveToNext();
private:
const LayoutObject* Find(const LayoutObject* child) const;
const LayoutObject* current_object_ = nullptr;
const LayoutInline* layout_inline_ = nullptr;
};
void MoveToFirstForCulledInline(const LayoutInline& layout_inline);
void MoveToNextForCulledInline();
void MoveToNextCulledInlineDescendantIfNeeded();
NGInlineCursorPosition current_;
ItemsSpan items_;
const NGFragmentItems* fragment_items_ = nullptr;
const NGPaintFragment* root_paint_fragment_ = nullptr;
CulledInlineTraversal culled_inline_;
// Used to traverse multiple |NGFragmentItems| when block fragmented.
const LayoutBlockFlow* root_block_flow_ = nullptr;
wtf_size_t fragment_index_ = 0;
wtf_size_t max_fragment_index_ = 0;
friend class NGInlineBackwardCursor;
};
// This class provides the |MoveToPreviousSibling| functionality, but as a
// separate class because it consumes memory, and only rarely used.
class CORE_EXPORT NGInlineBackwardCursor {
STACK_ALLOCATED();
public:
// |cursor| should be the first child of root or descendants, e.g. the first
// item in |NGInlineCursor::items_|.
explicit NGInlineBackwardCursor(const NGInlineCursor& cursor);
const NGInlineCursorPosition& Current() const { return current_; }
operator bool() const { return Current(); }
NGInlineCursor CursorForDescendants() const;
void MoveToPreviousSibling();
private:
NGInlineCursorPosition current_;
const NGInlineCursor& cursor_;
Vector<const NGPaintFragment*, 16> sibling_paint_fragments_;
Vector<NGInlineCursor::ItemsSpan::iterator, 16> sibling_item_iterators_;
wtf_size_t current_index_;
friend class NGInlineCursor;
};
CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGInlineCursor&);
CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGInlineCursor*);
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_CURSOR_H_