// Copyright 2017 The Chromium Authors
// 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_INLINE_LOGICAL_LINE_ITEM_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_INLINE_LOGICAL_LINE_ITEM_H_

#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/layout/geometry/logical_rect.h"
#include "third_party/blink/renderer/core/layout/inline/inline_item.h"
#include "third_party/blink/renderer/core/layout/inline/inline_item_result.h"
#include "third_party/blink/renderer/core/layout/inline/inline_item_text_index.h"
#include "third_party/blink/renderer/core/layout/inline/text_offset_range.h"
#include "third_party/blink/renderer/core/layout/layout_result.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
#include "third_party/blink/renderer/platform/wtf/gc_plugin.h"

namespace blink {

class LayoutObject;
class LayoutResult;

// This class represents an item in a line, after line break, but still mutable
// and in the logical coordinate system.
struct LogicalLineItem {
  DISALLOW_NEW();

  // Empty constructor needed for |resize()|.
  LogicalLineItem() = default;
  // Create a placeholder. A placeholder does not have a fragment nor a bidi
  // level.
  LogicalLineItem(LayoutUnit block_offset, LayoutUnit block_size)
      : rect(LayoutUnit(), block_offset, LayoutUnit(), block_size) {}
  // Crete a bidi control. A bidi control does not have a fragment, but has
  // bidi level and affects bidi reordering.
  explicit LogicalLineItem(UBiDiLevel bidi_level) : bidi_level(bidi_level) {}
  // Create an in-flow |LayoutResult|.
  LogicalLineItem(const LayoutResult* layout_result,
                  const LogicalRect& rect,
                  unsigned children_count,
                  UBiDiLevel bidi_level)
      : layout_result(layout_result),
        rect(rect),
        children_count(children_count),
        bidi_level(bidi_level) {}
  LogicalLineItem(const LayoutResult* layout_result,
                  LogicalOffset offset,
                  LayoutUnit inline_size,
                  unsigned children_count,
                  UBiDiLevel bidi_level)
      : layout_result(layout_result),
        rect(offset, LogicalSize()),
        inline_size(inline_size),
        children_count(children_count),
        bidi_level(bidi_level) {}
  // Create an in-flow text fragment.
  LogicalLineItem(const InlineItem& inline_item,
                  InlineItemResult& item_result,
                  const TextOffsetRange& text_offset,
                  LayoutUnit block_offset,
                  LayoutUnit inline_size,
                  LayoutUnit text_height,
                  UBiDiLevel bidi_level)
      : inline_item(&inline_item),
        shape_result(item_result.shape_result),
        text_offset(text_offset),
        rect(LayoutUnit(), block_offset, LayoutUnit(), text_height),
        inline_size(inline_size),
        bidi_level(bidi_level),
        has_only_bidi_trailing_spaces(
            item_result.has_only_bidi_trailing_spaces) {}
  LogicalLineItem(const InlineItem& inline_item,
                  const ShapeResultView* shape_result,
                  const TextOffsetRange& text_offset,
                  LayoutUnit block_offset,
                  LayoutUnit inline_size,
                  LayoutUnit text_height,
                  UBiDiLevel bidi_level)
      : inline_item(&inline_item),
        shape_result(shape_result),
        text_offset(text_offset),
        rect(LayoutUnit(), block_offset, LayoutUnit(), text_height),
        inline_size(inline_size),
        bidi_level(bidi_level) {}
  // Create a generated text.
  LogicalLineItem(const InlineItem& inline_item,
                  const ShapeResultView* shape_result,
                  const String& text_content,
                  LayoutUnit block_offset,
                  LayoutUnit inline_size,
                  LayoutUnit text_height,
                  UBiDiLevel bidi_level)
      : inline_item(&inline_item),
        shape_result(shape_result),
        text_offset({shape_result->StartIndex(), shape_result->EndIndex()}),
        text_content(text_content),
        rect(LayoutUnit(), block_offset, LayoutUnit(), text_height),
        inline_size(inline_size),
        bidi_level(bidi_level) {}
  // Create an ellipsis item. Ellipsis is a generated text, but does not have
  // corresponding |InlineItem| as it is generated by layout.
  LogicalLineItem(const LayoutObject& layout_object,
                  StyleVariant style_variant,
                  const ShapeResultView* shape_result,
                  const String& text_content,
                  const LogicalRect& rect,
                  UBiDiLevel bidi_level)
      : shape_result(shape_result),
        text_offset({shape_result->StartIndex(), shape_result->EndIndex()}),
        text_content(text_content),
        layout_object(&layout_object),
        style_variant(style_variant),
        rect(rect),
        inline_size(rect.size.inline_size),
        bidi_level(bidi_level) {}
  LogicalLineItem(const LogicalLineItem& source_item,
                  const ShapeResultView* shape_result,
                  const TextOffsetRange& text_offset)
      : inline_item(source_item.inline_item),
        shape_result(shape_result),
        text_offset(text_offset),
        text_content(source_item.text_content),
        rect(source_item.rect),
        inline_size(shape_result->SnappedWidth()),
        bidi_level(source_item.bidi_level) {}
  // Create an out-of-flow positioned object.
  LogicalLineItem(LayoutObject* out_of_flow_positioned_box,
                  UBiDiLevel bidi_level,
                  TextDirection container_direction)
      : out_of_flow_positioned_box(out_of_flow_positioned_box),
        bidi_level(bidi_level),
        container_direction(container_direction) {}
  // Create an unpositioned float.
  LogicalLineItem(LayoutObject* unpositioned_float,
                  UBiDiLevel bidi_level,
                  InlineItemTextIndex item_index)
      : unpositioned_float(unpositioned_float),
        item_index(item_index),
        bidi_level(bidi_level) {}
  // Create a positioned float.
  LogicalLineItem(const LayoutResult* layout_result,
                  BfcOffset bfc_offset,
                  UBiDiLevel bidi_level)
      : layout_result(layout_result),
        bfc_offset(bfc_offset),
        bidi_level(bidi_level) {}

  bool IsItemType(InlineItem::InlineItemType type) const {
    return inline_item && inline_item->Type() == type;
  }
  bool IsNotItemType(InlineItem::InlineItemType type) const {
    return inline_item && inline_item->Type() != type;
  }
  bool IsFloating() const {
    return layout_result && layout_result->GetPhysicalFragment().IsFloating();
  }
  bool IsInitialLetterBox() const {
    return layout_result &&
           layout_result->GetPhysicalFragment().IsInitialLetterBox();
  }
  bool IsInlineBox() const {
    return layout_result && layout_result->GetPhysicalFragment().IsInlineBox();
  }
  bool HasInFlowFragment() const {
    return IsNotItemType(InlineItem::kRubyLinePlaceholder) ||
           (layout_result &&
            !layout_result->GetPhysicalFragment().IsFloating());
  }
  bool HasInFlowOrFloatingFragment() const {
    return IsNotItemType(InlineItem::kRubyLinePlaceholder) || layout_result ||
           layout_object;
  }
  bool HasOutOfFlowFragment() const {
    return out_of_flow_positioned_box != nullptr;
  }
  bool HasFragment() const {
    return HasInFlowOrFloatingFragment() || HasOutOfFlowFragment();
  }
  bool IsControl() const { return IsItemType(InlineItem::kControl); }
  bool CanCreateFragmentItem() const { return HasInFlowOrFloatingFragment(); }
  bool HasBidiLevel() const { return bidi_level != 0xff; }
  bool IsPlaceholder() const { return !HasFragment() && !HasBidiLevel(); }
  bool IsOpaqueToBidiReordering() const {
    if (IsPlaceholder())
      return true;
    // Skip all inline boxes. Fragments for inline boxes maybe created earlier
    // if they have no children.
    if (layout_result) {
      DCHECK(layout_result->GetPhysicalFragment().GetLayoutObject());
      if (layout_result->GetPhysicalFragment()
              .GetLayoutObject()
              ->IsLayoutInline()) {
        return true;
      }
    }
    return false;
  }
  bool IsRubyLinePlaceholder() const {
    return IsItemType(InlineItem::kRubyLinePlaceholder);
  }

  const LogicalOffset& Offset() const { return rect.offset; }
  LayoutUnit InlineOffset() const { return rect.offset.inline_offset; }
  LayoutUnit BlockOffset() const { return rect.offset.block_offset; }
  LayoutUnit BlockEndOffset() const { return rect.BlockEndOffset(); }
  const LogicalSize& Size() const { return rect.size; }
  LogicalSize MarginSize() const { return {inline_size, Size().block_size}; }

  const PhysicalFragment* GetPhysicalFragment() const {
    if (layout_result)
      return &layout_result->GetPhysicalFragment();
    return nullptr;
  }
  const LayoutObject* GetLayoutObject() const;
  LayoutObject* GetMutableLayoutObject() const;
  const Node* GetNode() const;
  const ComputedStyle* Style() const;

  unsigned StartOffset() const { return text_offset.start; }
  unsigned EndOffset() const { return text_offset.end; }

  TextDirection ResolvedDirection() const {
    // Inline boxes are not leaves that they don't have directions.
    DCHECK(HasBidiLevel() || IsInlineBox());
    return HasBidiLevel() ? DirectionFromLevel(bidi_level)
                          : TextDirection::kLtr;
  }

  void Trace(Visitor*) const;

  // Data to create a text fragment from.
  // |inline_item| is null only for ellipsis items.
  Member<const InlineItem> inline_item;
  Member<const ShapeResultView> shape_result;
  TextOffsetRange text_offset;

  // Data to create a generated text fragment.
  String text_content;

  Member<const LayoutResult> layout_result;

  // Ellipsis does not have |InlineItem|, but built from |LayoutObject| and
  // |StyleVariant|.
  Member<const LayoutObject> layout_object;
  // Used only when |layout_object_| is not null.
  StyleVariant style_variant = StyleVariant::kStandard;

  Member<LayoutObject> out_of_flow_positioned_box;
  Member<LayoutObject> unpositioned_float;
  InlineItemTextIndex item_index;

  // The offset of the border box, initially in this child coordinate system.
  // |ComputeInlinePositions()| converts it to the offset within the line box.
  LogicalRect rect;
  // The offset of a positioned float wrt. the root BFC. This should only be
  // set for positioned floats.
  BfcOffset bfc_offset;
  // The inline size of the margin box.
  LayoutUnit inline_size;
  LayoutUnit margin_line_left;
  // The index of |box_data_list_|, used in |PrepareForReorder()| and
  // |UpdateAfterReorder()| to track children of boxes across BiDi reorder.
  unsigned box_data_index = 0;
  // For an inline box, shows the number of descendant |Child|ren, including
  // empty ones. Includes itself, so 1 means no descendants. 0 if not an
  // inline box. Available only after |CreateBoxFragments()|.
  unsigned children_count = 0;
  UBiDiLevel bidi_level = 0xff;
  // The current text direction for OOF positioned items.
  TextDirection container_direction = TextDirection::kLtr;
  // When an item contains only trailing spaces, the original bidi level needs
  // to be ignored, and just use paragraph direction (UAX#9 L1)
  bool has_only_bidi_trailing_spaces = false;

  bool is_hidden_for_paint = false;

  bool has_over_annotation = false;
  bool has_under_annotation = false;
};

CORE_EXPORT std::ostream& operator<<(std::ostream& stream,
                                     const LogicalLineItem& item);

// A vector of Child.
// Unlike the fragment builder, children are mutable.
// Callers can add to the fragment builder in a batch once finalized.
class CORE_EXPORT LogicalLineItems : public GarbageCollected<LogicalLineItems> {
 public:
  LogicalLineItems() = default;
  ~LogicalLineItems() { DCHECK(IsEmpty()); }
  void operator=(LogicalLineItems&& other) {
    children_ = std::move(other.children_);
  }

  LogicalLineItem& operator[](wtf_size_t i) { return children_[i]; }
  const LogicalLineItem& operator[](wtf_size_t i) const { return children_[i]; }

  wtf_size_t size() const { return children_.size(); }
  void clear() { children_.clear(); }
  bool IsEmpty() const { return children_.empty(); }
  void ReserveInitialCapacity(unsigned capacity) {
    children_.ReserveInitialCapacity(capacity);
  }
  void Shrink(wtf_size_t size) { children_.Shrink(size); }
  void swap(LogicalLineItems& other) { children_.swap(other.children_); }

  explicit operator base::span<LogicalLineItem>() {
    return base::span(children_);
  }
  using iterator = HeapVector<LogicalLineItem, 16>::iterator;
  iterator begin() { return children_.begin(); }
  iterator end() { return children_.end(); }
  using const_iterator = HeapVector<LogicalLineItem, 16>::const_iterator;
  const_iterator begin() const { return children_.begin(); }
  const_iterator end() const { return children_.end(); }
  using reverse_iterator = HeapVector<LogicalLineItem, 16>::reverse_iterator;
  reverse_iterator rbegin() { return children_.rbegin(); }
  reverse_iterator rend() { return children_.rend(); }
  using const_reverse_iterator =
      HeapVector<LogicalLineItem, 16>::const_reverse_iterator;
  const_reverse_iterator rbegin() const { return children_.rbegin(); }
  const_reverse_iterator rend() const { return children_.rend(); }

  LogicalLineItem* FirstInFlowChild();
  LogicalLineItem* LastInFlowChild();

  const LayoutResult* BlockInInlineLayoutResult() const;

  // Add a child. Accepts all constructor arguments for |LogicalLineItem|.
  template <class... Args>
  void AddChild(Args&&... args) {
    children_.emplace_back(std::forward<Args>(args)...);
  }
  void InsertChild(unsigned index, LogicalLineItem&& item) {
    WillInsertChild(index);
    children_.insert(index, item);
  }
  void InsertChild(unsigned index,
                   const LayoutResult* layout_result,
                   const LogicalRect& rect,
                   unsigned children_count) {
    WillInsertChild(index);
    children_.insert(
        index, LogicalLineItem(std::move(layout_result), rect, children_count,
                               /* bidi_level */ 0));
  }

  void MoveInInlineDirection(LayoutUnit);
  void MoveInInlineDirection(LayoutUnit, unsigned start, unsigned end);
  void MoveInBlockDirection(LayoutUnit);
  void MoveInBlockDirection(LayoutUnit, unsigned start, unsigned end);

  void SetPropagated() { was_propagated_ = true; }
  // Returns true if box fragments were created and were propagated to the
  // parent.
  bool WasPropagated() const { return was_propagated_; }

  void Trace(Visitor*) const;

 private:
  void WillInsertChild(unsigned index);

  HeapVector<LogicalLineItem, 16> children_;
  bool was_propagated_ = false;
};

}  // namespace blink

WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::LogicalLineItem)

#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_INLINE_LOGICAL_LINE_ITEM_H_
