text: // Copyright 2014 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/paint/inline_text_box_painter.h"
  
  #include "third_party/abseil-cpp/absl/types/optional.h"
  #include "third_party/blink/renderer/core/content_capture/content_capture_manager.h"
  #include "third_party/blink/renderer/core/editing/editor.h"
  #include "third_party/blink/renderer/core/editing/markers/composition_marker.h"
  #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
  #include "third_party/blink/renderer/core/editing/markers/text_match_marker.h"
  #include "third_party/blink/renderer/core/frame/local_frame.h"
  #include "third_party/blink/renderer/core/layout/api/line_layout_api_shim.h"
  #include "third_party/blink/renderer/core/layout/api/line_layout_box.h"
  #include "third_party/blink/renderer/core/layout/layout_text_combine.h"
  #include "third_party/blink/renderer/core/layout/layout_theme.h"
  #include "third_party/blink/renderer/core/layout/line/inline_text_box.h"
  #include "third_party/blink/renderer/core/layout/text_decoration_offset.h"
  #include "third_party/blink/renderer/core/paint/applied_decoration_painter.h"
  #include "third_party/blink/renderer/core/paint/document_marker_painter.h"
  #include "third_party/blink/renderer/core/paint/highlight_painting_utils.h"
  #include "third_party/blink/renderer/core/paint/paint_info.h"
  #include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
  #include "third_party/blink/renderer/core/paint/selection_bounds_recorder.h"
  #include "third_party/blink/renderer/core/paint/text_decoration_info.h"
  #include "third_party/blink/renderer/core/paint/text_painter.h"
  #include "third_party/blink/renderer/platform/graphics/dom_node_id.h"
  #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
  #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
  #include "third_party/blink/renderer/platform/graphics/paint/paint_record.h"
  #include "third_party/blink/renderer/platform/graphics/paint/paint_recorder.h"
  #include "third_party/blink/renderer/platform/graphics/paint/paint_shader.h"
  #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
  #include "third_party/skia/include/effects/SkGradientShader.h"
  
  namespace blink {
  
  class HTMLAnchorElement;
  
  namespace {
  
  // If an inline text box is truncated by an ellipsis, text box markers paint
  // over the ellipsis and other marker types don't. Other marker types that want
  // the normal behavior should use MarkerPaintStartAndEnd().
  std::pair<unsigned, unsigned> GetTextMatchMarkerPaintOffsets(
      const DocumentMarker& marker,
      const InlineTextBox& text_box) {
    // text_box.Start() returns an offset relative to the start of the layout
    // object. We add the LineLayoutItem's TextStartOffset() to get a DOM offset
    // (which is what DocumentMarker uses). This is necessary to get proper
    // behavior with the :first-letter psuedo element.
    const unsigned text_box_start =
        text_box.Start() + text_box.GetLineLayoutItem().TextStartOffset();
  
    DCHECK(marker.GetType() == DocumentMarker::kTextMatch ||
           marker.GetType() == DocumentMarker::kTextFragment);
    const unsigned start_offset = marker.StartOffset() > text_box_start
                                      ? marker.StartOffset() - text_box_start
                                      : 0U;
    const unsigned end_offset =
        std::min(marker.EndOffset() - text_box_start, text_box.Len());
    return std::make_pair(start_offset, end_offset);
  }
  
  DOMNodeId GetNodeHolder(Node* node) {
    if (node && node->GetLayoutObject())
      return To<LayoutText>(node->GetLayoutObject())->EnsureNodeId();
    return kInvalidDOMNodeId;
  }
  
  }  // anonymous namespace
  
  static LineLayoutItem EnclosingUnderlineObject(
      const InlineTextBox* inline_text_box) {
    bool first_line = inline_text_box->IsFirstLineStyle();
    for (LineLayoutItem current = inline_text_box->Parent()->GetLineLayoutItem();
         ;) {
      if (current.IsLayoutBlock())
        return current;
      if (!current.IsLayoutInline() || current.IsRubyText())
        return nullptr;
  
      const ComputedStyle& style_to_use = current.StyleRef(first_line);
      if (EnumHasFlags(style_to_use.GetTextDecoration(),
                       TextDecoration::kUnderline))
        return current;
  
      current = current.Parent();
      if (!current)
        return current;
  
      if (Node* node = current.GetNode()) {
        if (IsA<HTMLAnchorElement>(node) ||
            node->HasTagName(html_names::kFontTag))
          return current;
      }
    }
  }
  
  LayoutObject& InlineTextBoxPainter::InlineLayoutObject() const {
    return *LineLayoutAPIShim::LayoutObjectFrom(
        inline_text_box_.GetLineLayoutItem());
  }
  
  static void ComputeOriginAndWidthForBox(const InlineTextBox& box,
                                          PhysicalOffset& local_origin,
                                          LayoutUnit& width) {
    if (box.Truncation() != kCNoTruncation) {
      bool ltr = box.IsLeftToRightDirection();
      bool flow_is_ltr =
          box.GetLineLayoutItem().StyleRef().IsLeftToRightDirection();
      width = LayoutUnit(box.GetLineLayoutItem().Width(
          ltr == flow_is_ltr ? box.Start() : box.Start() + box.Truncation(),
          ltr == flow_is_ltr ? box.Truncation() : box.Len() - box.Truncation(),
          box.TextPos(), flow_is_ltr ? TextDirection::kLtr : TextDirection::kRtl,
          box.IsFirstLineStyle()));
      if (!flow_is_ltr)
        local_origin += PhysicalOffset(box.LogicalWidth() - width, LayoutUnit());
    }
  }
  
  void InlineTextBoxPainter::Paint(const PaintInfo& paint_info,
                                   const PhysicalOffset& paint_offset) {
    // We can skip painting if the text box is empty and has no selection.
    if (inline_text_box_.Truncation() == kCFullTruncation ||
        !inline_text_box_.Len())
      return;
  
    const auto& style_to_use = inline_text_box_.GetLineLayoutItem().StyleRef(
        inline_text_box_.IsFirstLineStyle());
    if (style_to_use.Visibility() != EVisibility::kVisible)
      return;
  
    DCHECK(!ShouldPaintSelfOutline(paint_info.phase) &&
           !ShouldPaintDescendantOutlines(paint_info.phase));
  
    bool is_printing =
        inline_text_box_.GetLineLayoutItem().GetDocument().Printing();
  
    // Determine whether or not we're selected.
    bool have_selection = !is_printing &&
                          paint_info.phase != PaintPhase::kTextClip &&
                          inline_text_box_.IsSelected();
    if (!have_selection && paint_info.phase == PaintPhase::kSelectionDragImage) {
      // When only painting the selection, don't bother to paint if there is none.
      return;
    }
  
    PhysicalRect physical_overflow = inline_text_box_.PhysicalOverflowRect();
    if (!paint_info.IntersectsCullRect(physical_overflow, paint_offset) &&
        !have_selection)
      return;
  
    physical_overflow.Move(paint_offset);
    IntRect visual_rect = EnclosingIntRect(physical_overflow);
  
    GraphicsContext& context = paint_info.context;
    PhysicalOffset box_origin =
        inline_text_box_.PhysicalLocation() + paint_offset;
  
    // We round the y-axis to ensure consistent line heights.
    if (inline_text_box_.IsHorizontal()) {
      box_origin.top = LayoutUnit(box_origin.top.Round());
    } else {
      box_origin.left = LayoutUnit(box_origin.left.Round());
    }
  
    // If vertical, |box_rect| is in the physical coordinates space under the
    // rotation transform.
    PhysicalRect box_rect(box_origin,
                          PhysicalSize(inline_text_box_.LogicalWidth(),
                                       inline_text_box_.LogicalHeight()));
  
    absl::optional<SelectionBoundsRecorder> selection_recorder;
    if (have_selection && paint_info.phase == PaintPhase::kForeground &&
        !is_printing) {
      const FrameSelection& frame_selection =
          InlineLayoutObject().GetFrame()->Selection();
      SelectionState selection_state =
          frame_selection.ComputeLayoutSelectionStateForInlineTextBox(
              inline_text_box_);
      if (SelectionBoundsRecorder::ShouldRecordSelection(frame_selection,
                                                         selection_state)) {
        PhysicalRect selection_rect =
            GetSelectionRect<InlineTextBoxPainter::PaintOptions::kNormal>(
                context, box_rect, style_to_use, style_to_use.GetFont());
  
        TextDirection direction = inline_text_box_.IsLeftToRightDirection()
                                      ? TextDirection::kLtr
                                      : TextDirection::kRtl;
        // We need to account for vertical writing mode rotation - for the
        // actual painting of the selection_rect, this is done below by
        // concatenating a rotation matrix on the context.
        if (!style_to_use.IsHorizontalWritingMode()) {
          FloatRect rotated_selection =
              TextPainterBase::Rotation(box_rect, TextPainterBase::kClockwise)
                  .MapRect(static_cast<FloatRect>(selection_rect));
          selection_rect = PhysicalRect::EnclosingRect(rotated_selection);
        }
        selection_recorder.emplace(
            selection_state, selection_rect, context.GetPaintController(),
            direction, style_to_use.GetWritingMode(), InlineLayoutObject());
      }
    }
  
    // The text clip phase already has a DrawingRecorder. Text clips are initiated
    // only in BoxPainter::PaintFillLayer, which is already within a
    // DrawingRecorder.
    absl::optional<DrawingRecorder> recorder;
    if (paint_info.phase != PaintPhase::kTextClip) {
      if (DrawingRecorder::UseCachedDrawingIfPossible(context, inline_text_box_,
                                                      paint_info.phase))
        return;
      recorder.emplace(context, inline_text_box_, paint_info.phase, visual_rect);
    }
  
    unsigned length = inline_text_box_.Len();
    const String& layout_item_string =
        inline_text_box_.GetLineLayoutItem().GetText();
  
    String first_line_string;
    if (inline_text_box_.IsFirstLineStyle()) {
      first_line_string = layout_item_string;
      const ComputedStyle& style = inline_text_box_.GetLineLayoutItem().StyleRef(
          inline_text_box_.IsFirstLineStyle());
      style.ApplyTextTransform(
          &first_line_string,
          inline_text_box_.GetLineLayoutItem().PreviousCharacter());
      // TODO(crbug.com/795498): this is a hack. The root issue is that
      // capitalizing letters can change the length of the backing string.
      // That needs to be taken into account when computing the size of the box
      // or its painting.
      if (inline_text_box_.Start() >= first_line_string.length())
        return;
      length =
          std::min(length, first_line_string.length() - inline_text_box_.Start());
  
      // TODO(szager): Figure out why this CHECK sometimes fails, it shouldn't.
      CHECK_LE(inline_text_box_.Start() + length, first_line_string.length());
    } else {
      // TODO(szager): Figure out why this CHECK sometimes fails, it shouldn't.
      CHECK_LE(inline_text_box_.Start() + length, layout_item_string.length());
    }
    StringView string =
        StringView(inline_text_box_.IsFirstLineStyle() ? first_line_string
                                                       : layout_item_string,
                   inline_text_box_.Start(), length);
    int maximum_length = inline_text_box_.GetLineLayoutItem().TextLength() -
                         inline_text_box_.Start();
  
    StringBuilder characters_with_hyphen;
    TextRun text_run = inline_text_box_.ConstructTextRun(
        style_to_use, string, maximum_length,
        inline_text_box_.HasHyphen() ? &characters_with_hyphen : nullptr);
    if (inline_text_box_.HasHyphen())
      length = text_run.length();
  
    absl::optional<AffineTransform> rotation;
    absl::optional<GraphicsContextStateSaver> state_saver;
    LayoutTextCombine* combined_text = nullptr;
    if (!inline_text_box_.IsHorizontal()) {
      if (style_to_use.HasTextCombine() &&
          inline_text_box_.GetLineLayoutItem().IsCombineText()) {
        combined_text = &To<LayoutTextCombine>(InlineLayoutObject());
        if (!combined_text->IsCombined())
          combined_text = nullptr;
      }
      if (combined_text) {
        box_rect.SetWidth(combined_text->InlineWidthForLayout());
        // Justfication applies to before and after the combined text as if
        // it is an ideographic character, and is prohibited inside the
        // combined text.
        if (float expansion = text_run.Expansion()) {
          text_run.SetExpansion(0);
          if (text_run.AllowsLeadingExpansion()) {
            if (text_run.AllowsTrailingExpansion())
              expansion /= 2;
            PhysicalOffset offset(LayoutUnit(),
                                  LayoutUnit::FromFloatRound(expansion));
            box_origin += offset;
            box_rect.Move(offset);
          }
        }
      } else {
        rotation.emplace(
            TextPainterBase::Rotation(box_rect, TextPainterBase::kClockwise));
        state_saver.emplace(context);
        context.ConcatCTM(*rotation);
      }
    }
  
    // Determine text colors.
    TextPaintStyle text_style = TextPainterBase::TextPaintingStyle(
        inline_text_box_.GetLineLayoutItem().GetDocument(), style_to_use,
        paint_info);
    TextPaintStyle selection_style = text_style;
    if (have_selection) {
      selection_style = TextPainterBase::SelectionPaintingStyle(
          inline_text_box_.GetLineLayoutItem().GetDocument(), style_to_use,
          inline_text_box_.GetLineLayoutItem().GetNode(), paint_info, text_style);
    }
    bool paint_selected_text_only =
        (paint_info.phase == PaintPhase::kSelectionDragImage);
    bool paint_selected_text_separately =
        !paint_selected_text_only && text_style != selection_style;
  
    // Set our font.
    const Font& font = style_to_use.GetFont();
    const SimpleFontData* font_data = font.PrimaryFont();
    DCHECK(font_data);
  
    int ascent = font_data ? font_data->GetFontMetrics().Ascent() : 0;
    PhysicalOffset text_origin(box_origin.left, box_origin.top + ascent);
  
    const DocumentMarkerVector& markers_to_paint = ComputeMarkersToPaint();
  
    // 1. Paint backgrounds behind text if needed. Examples of such backgrounds
    // include selection and composition highlights.
    if (paint_info.phase != PaintPhase::kSelectionDragImage &&
        paint_info.phase != PaintPhase::kTextClip && !is_printing) {
      PaintDocumentMarkers(markers_to_paint, paint_info, box_origin, style_to_use,
                           font, DocumentMarkerPaintPhase::kBackground);
      if (have_selection) {
        PhysicalRect selection_rect;
        if (combined_text) {
          selection_rect =
              PaintSelection<InlineTextBoxPainter::PaintOptions::kCombinedText>(
                  context, box_rect, style_to_use, font,
                  selection_style.fill_color, combined_text);
        } else {
          selection_rect =
              PaintSelection<InlineTextBoxPainter::PaintOptions::kNormal>(
                  context, box_rect, style_to_use, font,
                  selection_style.fill_color);
        }
  
        if (recorder && !box_rect.Contains(selection_rect)) {
          IntRect selection_visual_rect = EnclosingIntRect(selection_rect);
          if (rotation)
            selection_visual_rect = rotation->MapRect(selection_visual_rect);
          recorder->UniteVisualRect(selection_visual_rect);
        }
      }
    }
  
    // 2. Now paint the foreground, including text and decorations.
    int selection_start = 0;
    int selection_end = 0;
    if (paint_selected_text_only || paint_selected_text_separately)
      inline_text_box_.SelectionStartEnd(selection_start, selection_end);
  
    bool respect_hyphen =
        selection_end == static_cast<int>(inline_text_box_.Len()) &&
        inline_text_box_.HasHyphen();
    if (respect_hyphen)
      selection_end = text_run.length();
  
    bool ltr = inline_text_box_.IsLeftToRightDirection();
    bool flow_is_ltr = inline_text_box_.GetLineLayoutItem()
                           .ContainingBlock()
                           .StyleRef()
                           .IsLeftToRightDirection();
  
    const PaintOffsets& selection_offsets =
        ApplyTruncationToPaintOffsets({static_cast<unsigned>(selection_start),
                                       static_cast<unsigned>(selection_end)});
    selection_start = selection_offsets.start;
    selection_end = selection_offsets.end;
    if (have_selection) {
      font.ExpandRangeToIncludePartialGlyphs(text_run, &selection_start,
                                             &selection_end);
    }
  
    if (inline_text_box_.Truncation() != kCNoTruncation) {
      // In a mixed-direction flow the ellipsis is at the start of the text
      // rather than at the end of it.
      length =
          ltr == flow_is_ltr ? inline_text_box_.Truncation() : text_run.length();
    }
  
    TextPainter text_painter(context, font, text_run, text_origin, box_rect,
                             inline_text_box_.IsHorizontal());
    TextEmphasisPosition emphasis_mark_position;
    bool has_text_emphasis = inline_text_box_.GetEmphasisMarkPosition(
        style_to_use, emphasis_mark_position);
    if (has_text_emphasis)
      text_painter.SetEmphasisMark(style_to_use.TextEmphasisMarkString(),
                                   emphasis_mark_position);
    if (combined_text)
      text_painter.SetCombinedText(combined_text);
    if (inline_text_box_.Truncation() != kCNoTruncation && ltr != flow_is_ltr)
      text_painter.SetEllipsisOffset(inline_text_box_.Truncation());
  
    DOMNodeId node_id = GetNodeHolder(
        LineLayoutAPIShim::LayoutObjectFrom(inline_text_box_.GetLineLayoutItem())
            ->GetNode());
  
    if (!paint_selected_text_only) {
      // Paint text decorations except line-through.
      absl::optional<TextDecorationInfo> decoration_info;
      bool has_line_through_decoration = false;
      if (style_to_use.TextDecorationsInEffect() != TextDecoration::kNone &&
          inline_text_box_.Truncation() != kCFullTruncation) {
        PhysicalOffset local_origin = box_origin;
        LayoutUnit width = inline_text_box_.LogicalWidth();
        ComputeOriginAndWidthForBox(inline_text_box_, local_origin, width);
        const LineLayoutItem& decorating_box =
            EnclosingUnderlineObject(&inline_text_box_);
        const ComputedStyle* decorating_box_style =
            decorating_box ? decorating_box.Style() : nullptr;
        absl::optional<AppliedTextDecoration> selection_text_decoration =
            UNLIKELY(have_selection)
                ? absl::optional<AppliedTextDecoration>(
                      selection_style.selection_text_decoration)
                : absl::nullopt;
        decoration_info.emplace(
            local_origin, width, inline_text_box_.Root().BaselineType(),
            style_to_use, selection_text_decoration, decorating_box_style);
        TextDecorationOffset decoration_offset(decoration_info->Style(),
                                               &inline_text_box_, decorating_box);
        text_painter.PaintDecorationsExceptLineThrough(
            decoration_offset, decoration_info.value(), paint_info,
            style_to_use.AppliedTextDecorations(), text_style,
            &has_line_through_decoration);
      }
  
      int start_offset = 0;
      int end_offset = length;
      // Where the text and its flow have opposite directions then our offset into
      // the text given by |truncation| is at the start of the part that will be
      // visible.
      if (inline_text_box_.Truncation() != kCNoTruncation && ltr != flow_is_ltr) {
        start_offset = inline_text_box_.Truncation();
        end_offset = text_run.length();
      }
  
      if (paint_selected_text_separately && selection_start < selection_end) {
        start_offset = selection_end;
        end_offset = selection_start;
      }
      text_painter.Paint(start_offset, end_offset, length, text_style, node_id);
  
      // Paint line-through decoration if needed.
      if (has_line_through_decoration) {
        text_painter.PaintDecorationsOnlyLineThrough(
            decoration_info.value(), paint_info,
            style_to_use.AppliedTextDecorations(), text_style);
      }
    }
  
    if ((paint_selected_text_only || paint_selected_text_separately) &&
        selection_start < selection_end) {
      // paint only the text that is selected.
      // Because only a part of the text glyph can be selected, we need to draw
      // the selection twice:
      PhysicalRect selection_rect =
          GetSelectionRect<InlineTextBoxPainter::PaintOptions::kNormal>(
              context, box_rect, style_to_use, font);
  
      // the first time, we draw the glyphs outside the selection area, with
      // the original style.
      {
        GraphicsContextStateSaver inner_state_saver(context);
        context.ClipOut(FloatRect(selection_rect));
        text_painter.Paint(selection_start, selection_end, length, text_style,
                           node_id);
      }
      // the second time, we draw the glyphs inside the selection area, with
      // the selection style.
      {
        GraphicsContextStateSaver inner_state_saver(context);
        context.Clip(FloatRect(selection_rect));
        text_painter.Paint(selection_start, selection_end, length,
                           selection_style, node_id);
      }
    }
  
    if (paint_info.phase == PaintPhase::kForeground) {
      PaintDocumentMarkers(markers_to_paint, paint_info, box_origin, style_to_use,
                           font, DocumentMarkerPaintPhase::kForeground);
    }
  
    if (!font.ShouldSkipDrawing())
      PaintTimingDetector::NotifyTextPaint(visual_rect);
  }
  
  InlineTextBoxPainter::PaintOffsets
  InlineTextBoxPainter::ApplyTruncationToPaintOffsets(
      const InlineTextBoxPainter::PaintOffsets& offsets) {
    const uint16_t truncation = inline_text_box_.Truncation();
    if (truncation == kCNoTruncation)
      return offsets;
  
    // If we're in mixed-direction mode (LTR text in an RTL box or vice-versa),
    // the truncation ellipsis is at the *start* of the text box rather than the
    // end.
    bool ltr = inline_text_box_.IsLeftToRightDirection();
    bool flow_is_ltr = inline_text_box_.GetLineLayoutItem()
                           .ContainingBlock()
                           .StyleRef()
                           .IsLeftToRightDirection();
  
    // truncation is relative to the start of the InlineTextBox, not the text
    // node.
    if (ltr == flow_is_ltr) {
      return {std::min<unsigned>(offsets.start, truncation),
              std::min<unsigned>(offsets.end, truncation)};
    }
  
    return {std::max<unsigned>(offsets.start, truncation),
            std::max<unsigned>(offsets.end, truncation)};
  }
  
  InlineTextBoxPainter::PaintOffsets InlineTextBoxPainter::MarkerPaintStartAndEnd(
      const DocumentMarker& marker) {
    // Text match markers are painted differently (in an inline text box truncated
    // by an ellipsis, they paint over the ellipsis) and so should not use this
    // function.
    DCHECK(marker.GetType() != DocumentMarker::kTextMatch &&
           marker.GetType() != DocumentMarker::kTextFragment);
    DCHECK(inline_text_box_.Truncation() != kCFullTruncation);
    DCHECK(inline_text_box_.Len());
  
    // inline_text_box_.Start() returns an offset relative to the start of the
    // layout object. We add the LineLayoutItem's TextStartOffset() to get a DOM
    // offset (which is what DocumentMarker uses). This is necessary to get proper
    // behavior with the :first-letter psuedo element.
    const unsigned inline_text_box_start =
        inline_text_box_.Start() +
        inline_text_box_.GetLineLayoutItem().TextStartOffset();
  
    // Start painting at the beginning of the text or the specified underline
    // start offset, whichever is greater.
    unsigned paint_start = std::max(inline_text_box_start, marker.StartOffset());
    // Cap the maximum paint start to the last character in the text box.
    paint_start = std::min(paint_start, inline_text_box_.end());
  
    // End painting just past the end of the text or the specified underline end
    // offset, whichever is less.
    unsigned paint_end = std::min(
        inline_text_box_.end() + 1,
        marker.EndOffset());  // end() points at the last char, not past it.
  
    // paint_start and paint_end are currently relative to the start of the text
    // node. Subtract to make them relative to the start of the InlineTextBox.
    paint_start -= inline_text_box_start;
    paint_end -= inline_text_box_start;
  
    return ApplyTruncationToPaintOffsets({paint_start, paint_end});
  }
  
  void InlineTextBoxPainter::PaintSingleMarkerBackgroundRun(
      GraphicsContext& context,
      const PhysicalOffset& box_origin,
      const ComputedStyle& style,
      const Font& font,
      Color background_color,
      int start_pos,
      int end_pos) {
    if (background_color == Color::kTransparent)
      return;
  
    int delta_y = (inline_text_box_.GetLineLayoutItem()
                           .StyleRef()
                           .IsFlippedLinesWritingMode()
                       ? inline_text_box_.Root().SelectionBottom() -
                             inline_text_box_.LogicalBottom()
                       : inline_text_box_.LogicalTop() -
                             inline_text_box_.Root().SelectionTop())
                      .ToInt();
    int sel_height = inline_text_box_.Root().SelectionHeight().ToInt();
    FloatPoint local_origin(box_origin.left.ToFloat(),
                            box_origin.top.ToFloat() - delta_y);
    context.DrawHighlightForText(font, inline_text_box_.ConstructTextRun(style),
                                 local_origin, sel_height, background_color,
                                 start_pos, end_pos);
  }
  
  DocumentMarkerVector InlineTextBoxPainter::ComputeMarkersToPaint() const {
    Node* const node = inline_text_box_.GetLineLayoutItem().GetNode();
    auto* text_node = DynamicTo<Text>(node);
    if (!text_node)
      return DocumentMarkerVector();
  
    DocumentMarkerController& document_marker_controller =
        inline_text_box_.GetLineLayoutItem().GetDocument().Markers();
    return document_marker_controller.ComputeMarkersToPaint(*text_node);
  }
  
  void InlineTextBoxPainter::PaintDocumentMarkers(
      const DocumentMarkerVector& markers_to_paint,
      const PaintInfo& paint_info,
      const PhysicalOffset& box_origin,
      const ComputedStyle& style,
      const Font& font,
      DocumentMarkerPaintPhase marker_paint_phase) {
    if (!inline_text_box_.GetLineLayoutItem().GetNode())
      return;
  
    DCHECK(inline_text_box_.Truncation() != kCFullTruncation);
    DCHECK(inline_text_box_.Len());
  
    DocumentMarkerVector::const_iterator marker_it = markers_to_paint.begin();
    // Give any document markers that touch this run a chance to draw before the
    // text has been drawn.  Note end() points at the last char, not one past it
    // like endOffset and ranges do.
    for (; marker_it != markers_to_paint.end(); ++marker_it) {
      DCHECK(*marker_it);
      const DocumentMarker& marker = **marker_it;
  
      if (marker.EndOffset() <= inline_text_box_.Start()) {
        // marker is completely before this run.  This might be a marker that sits
        // before the first run we draw, or markers that were within runs we
        // skipped due to truncation.
        continue;
      }
      if (marker.StartOffset() > inline_text_box_.end()) {
        // marker is completely after this run, bail.  A later run will paint it.
        continue;
      }
  
      // marker intersects this run.  Paint it.
      switch (marker.GetType()) {
        case DocumentMarker::kSpelling:
          if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground)
            continue;
          inline_text_box_.PaintDocumentMarker(paint_info, box_origin, marker,
                                               style, font, false);
          break;
        case DocumentMarker::kGrammar:
          if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground)
            continue;
          inline_text_box_.PaintDocumentMarker(paint_info, box_origin, marker,
                                               style, font, true);
          break;
        case DocumentMarker::kTextFragment:
        case DocumentMarker::kTextMatch:
          if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground) {
            inline_text_box_.PaintTextMarkerBackground(
                paint_info, box_origin, To<TextMarkerBase>(marker), style, font);
          } else {
            inline_text_box_.PaintTextMarkerForeground(
                paint_info, box_origin, To<TextMarkerBase>(marker), style, font);
          }
          break;
        case DocumentMarker::kComposition:
        case DocumentMarker::kActiveSuggestion:
        case DocumentMarker::kSuggestion: {
          const auto& styleable_marker = To<StyleableMarker>(marker);
          if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground) {
            const PaintOffsets marker_offsets =
                MarkerPaintStartAndEnd(styleable_marker);
            PaintSingleMarkerBackgroundRun(
                paint_info.context, box_origin, style, font,
                styleable_marker.BackgroundColor(), marker_offsets.start,
                marker_offsets.end);
          } else if (DocumentMarkerPainter::ShouldPaintMarkerUnderline(
                         styleable_marker)) {
            PaintStyleableMarkerUnderline(paint_info.context, box_origin,
                                          styleable_marker, style, font);
          }
        } break;
        default:
          // Marker is not painted, or painting code has not been added yet
          break;
      }
    }
  }
  
  void InlineTextBoxPainter::PaintDocumentMarker(const PaintInfo& paint_info,
                                                 const PhysicalOffset& box_origin,
                                                 const DocumentMarker& marker,
                                                 const ComputedStyle& style,
                                                 const Font& font,
                                                 bool grammar) {
    if (inline_text_box_.GetLineLayoutItem().GetDocument().Printing())
      return;
  
    if (inline_text_box_.Truncation() == kCFullTruncation)
      return;
  
    LayoutUnit start;  // start of line to draw, relative to tx
    LayoutUnit width = inline_text_box_.LogicalWidth();  // how much line to draw
  
    // Determine whether we need to measure text
    bool marker_spans_whole_box = true;
    if (inline_text_box_.Start() <= marker.StartOffset())
      marker_spans_whole_box = false;
    if ((inline_text_box_.end() + 1) !=
        marker.EndOffset())  // end points at the last char, not past it
      marker_spans_whole_box = false;
    if (inline_text_box_.Truncation() != kCNoTruncation)
      marker_spans_whole_box = false;
  
    if (!marker_spans_whole_box || grammar) {
      const PaintOffsets& marker_offsets = MarkerPaintStartAndEnd(marker);
  
      // Calculate start & width
      int delta_y = (inline_text_box_.GetLineLayoutItem()
                             .StyleRef()
                             .IsFlippedLinesWritingMode()
                         ? inline_text_box_.Root().SelectionBottom() -
                               inline_text_box_.LogicalBottom()
                         : inline_text_box_.LogicalTop() -
                               inline_text_box_.Root().SelectionTop())
                        .ToInt();
      int sel_height = inline_text_box_.Root().SelectionHeight().ToInt();
      PhysicalOffset start_point(box_origin.left, box_origin.top - delta_y);
      TextRun run = inline_text_box_.ConstructTextRun(style);
  
      // FIXME: Convert the document markers to float rects.
      IntRect marker_rect = EnclosingIntRect(
          font.SelectionRectForText(run, FloatPoint(start_point), sel_height,
                                    marker_offsets.start, marker_offsets.end));
      start = marker_rect.X() - start_point.left;
      width = LayoutUnit(marker_rect.Width());
    }
    DocumentMarkerPainter::PaintDocumentMarker(
        paint_info, box_origin, style, marker.GetType(),
        PhysicalRect(start, LayoutUnit(), width,
                     inline_text_box_.LogicalHeight()));
  }
  
  template <InlineTextBoxPainter::PaintOptions options>
  PhysicalRect InlineTextBoxPainter::GetSelectionRect(
      GraphicsContext& context,
      const PhysicalRect& box_rect,
      const ComputedStyle& style,
      const Font& font,
      LayoutTextCombine* combined_text) {
    // See if we have a selection to paint at all.
    int start_pos, end_pos;
    inline_text_box_.SelectionStartEnd(start_pos, end_pos);
    if (start_pos >= end_pos)
      return PhysicalRect();
  
    // If the text is truncated, let the thing being painted in the truncation
    // draw its own highlight.
    unsigned start = inline_text_box_.Start();
    int length = inline_text_box_.Len();
    bool ltr = inline_text_box_.IsLeftToRightDirection();
    bool flow_is_ltr = inline_text_box_.GetLineLayoutItem()
                           .ContainingBlock()
                           .StyleRef()
                           .IsLeftToRightDirection();
    if (inline_text_box_.Truncation() != kCNoTruncation) {
      // In a mixed-direction flow the ellipsis is at the start of the text
      // so we need to start after it. Otherwise we just need to make sure
      // the end of the text is where the ellipsis starts.
      if (ltr != flow_is_ltr)
        start_pos = std::max<int>(start_pos, inline_text_box_.Truncation());
      else
        length = inline_text_box_.Truncation();
    }
    StringView string(inline_text_box_.GetLineLayoutItem().GetText(), start,
                      static_cast<unsigned>(length));
  
    StringBuilder characters_with_hyphen;
    bool respect_hyphen = end_pos == length && inline_text_box_.HasHyphen();
    TextRun text_run = inline_text_box_.ConstructTextRun(
        style, string,
        inline_text_box_.GetLineLayoutItem().TextLength() -
            inline_text_box_.Start(),
        respect_hyphen ? &characters_with_hyphen : nullptr);
    if (respect_hyphen)
      end_pos = text_run.length();
  
    if (options == InlineTextBoxPainter::PaintOptions::kCombinedText) {
      DCHECK(combined_text);
      // We can't use the height of m_inlineTextBox because LayoutTextCombine's
      // inlineTextBox is horizontal within vertical flow
      combined_text->TransformToInlineCoordinates(context, box_rect, true);
    }
  
    LayoutUnit selection_bottom = inline_text_box_.Root().SelectionBottom();
    LayoutUnit selection_top = inline_text_box_.Root().SelectionTop();
  
    int delta_y =
        RoundToInt(inline_text_box_.GetLineLayoutItem()
                           .StyleRef()
                           .IsFlippedLinesWritingMode()
                       ? selection_bottom - inline_text_box_.LogicalBottom()
                       : inline_text_box_.LogicalTop() - selection_top);
    int sel_height = std::max(0, RoundToInt(selection_bottom - selection_top));
  
    FloatPoint local_origin(box_rect.X().ToFloat(),
                            (box_rect.Y() - delta_y).ToFloat());
    PhysicalRect selection_rect =
        PhysicalRect::EnclosingRect(font.SelectionRectForText(
            text_run, local_origin, sel_height, start_pos, end_pos));
    // For line breaks, just painting a selection where the line break itself
    // is rendered is sufficient. Don't select it if there's an ellipsis
    // there.
    if (inline_text_box_.HasWrappedSelectionNewline() &&
        inline_text_box_.Truncation() == kCNoTruncation &&
        !inline_text_box_.IsLineBreak())
      ExpandToIncludeNewlineForSelection(selection_rect);
  
    // Line breaks report themselves as having zero width for layout purposes,
    // and so will end up positioned at (0, 0), even though we paint their
    // selection highlight with character width. For RTL then, we have to
    // explicitly shift the selection rect over to paint in the right location.
    if (!inline_text_box_.IsLeftToRightDirection() &&
        inline_text_box_.IsLineBreak())
      selection_rect.Move(PhysicalOffset(-selection_rect.Width(), LayoutUnit()));
    if (!flow_is_ltr && !ltr && inline_text_box_.Truncation() != kCNoTruncation) {
      selection_rect.Move(
          PhysicalOffset(inline_text_box_.LogicalWidth() - selection_rect.Width(),
                         LayoutUnit()));
    }
  
    return selection_rect;
  }
  
  template <InlineTextBoxPainter::PaintOptions options>
  PhysicalRect InlineTextBoxPainter::PaintSelection(
      GraphicsContext& context,
      const PhysicalRect& box_rect,
      const ComputedStyle& style,
      const Font& font,
      Color text_color,
      LayoutTextCombine* combined_text) {
    auto layout_item = inline_text_box_.GetLineLayoutItem();
    Color c = HighlightPaintingUtils::HighlightBackgroundColor(
        layout_item.GetDocument(), layout_item.StyleRef(), layout_item.GetNode(),
        kPseudoIdSelection);
    if (!c.Alpha())
      return PhysicalRect();
  
    PhysicalRect selection_rect =
        GetSelectionRect<options>(context, box_rect, style, font, combined_text);
  
    // If the text color ends up being the same as the selection background,
    // invert the selection background.
    if (text_color == c) {
      UseCounter::Count(layout_item.GetDocument(),
                        WebFeature::kSelectionBackgroundColorInversion);
      c = Color(0xff - c.Red(), 0xff - c.Green(), 0xff - c.Blue());
    }
  
    GraphicsContextStateSaver state_saver(context);
  
    context.FillRect(FloatRect(selection_rect), c);
    return selection_rect;
  }
  
  void InlineTextBoxPainter::ExpandToIncludeNewlineForSelection(
      PhysicalRect& rect) {
    FloatRectOutsets outsets = FloatRectOutsets();
    float space_width = inline_text_box_.NewlineSpaceWidth();
    if (inline_text_box_.IsLeftToRightDirection())
      outsets.SetRight(space_width);
    else
      outsets.SetLeft(space_width);
    rect.Expand(outsets);
  }
  
  void InlineTextBoxPainter::PaintStyleableMarkerUnderline(
      GraphicsContext& context,
      const PhysicalOffset& box_origin,
      const StyleableMarker& marker,
      const ComputedStyle& style,
      const Font& font) {
    if (inline_text_box_.Truncation() == kCFullTruncation)
      return;
  
    const PaintOffsets marker_offsets = MarkerPaintStartAndEnd(marker);
    const TextRun& run = inline_text_box_.ConstructTextRun(style);
    // Pass 0 for height since we only care about the width
    const FloatRect& marker_rect = font.SelectionRectForText(
        run, FloatPoint(), 0, marker_offsets.start, marker_offsets.end);
    DocumentMarkerPainter::PaintStyleableMarkerUnderline(
        context, box_origin, marker, style, marker_rect,
        inline_text_box_.LogicalHeight(),
        inline_text_box_.GetLineLayoutItem().GetDocument().InDarkMode());
  }
  
  void InlineTextBoxPainter::PaintTextMarkerForeground(
      const PaintInfo& paint_info,
      const PhysicalOffset& box_origin,
      const TextMarkerBase& marker,
      const ComputedStyle& style,
      const Font& font) {
    if (marker.GetType() == DocumentMarker::kTextMatch &&
        !InlineLayoutObject()
             .GetFrame()
             ->GetEditor()
             .MarkedTextMatchesAreHighlighted())
      return;
  
    const auto paint_offsets =
        GetTextMatchMarkerPaintOffsets(marker, inline_text_box_);
    TextRun run = inline_text_box_.ConstructTextRun(style);
  
    const SimpleFontData* font_data = font.PrimaryFont();
    DCHECK(font_data);
    if (!font_data)
      return;
  
    const TextPaintStyle text_style =
        DocumentMarkerPainter::ComputeTextPaintStyleFrom(
            inline_text_box_.GetLineLayoutItem().GetDocument(),
            inline_text_box_.GetLineLayoutItem().GetNode(), style, marker,
            paint_info);
    if (text_style.current_color == Color::kTransparent)
      return;
  
    // If vertical, |box_rect| is in the physical coordinates space under the
    // rotation transform.
    PhysicalRect box_rect(box_origin,
                          PhysicalSize(inline_text_box_.LogicalWidth(),
                                       inline_text_box_.LogicalHeight()));
    PhysicalOffset text_origin(
        box_origin.left, box_origin.top + font_data->GetFontMetrics().Ascent());
    TextPainter text_painter(paint_info.context, font, run, text_origin, box_rect,
                             inline_text_box_.IsHorizontal());
  
    text_painter.Paint(paint_offsets.first, paint_offsets.second,
                       inline_text_box_.Len(), text_style, kInvalidDOMNodeId);
  }
  
  void InlineTextBoxPainter::PaintTextMarkerBackground(
      const PaintInfo& paint_info,
      const PhysicalOffset& box_origin,
      const TextMarkerBase& marker,
      const ComputedStyle& style,
      const Font& font) {
    if (marker.GetType() == DocumentMarker::kTextMatch &&
        !LineLayoutAPIShim::LayoutObjectFrom(inline_text_box_.GetLineLayoutItem())
             ->GetFrame()
             ->GetEditor()
             .MarkedTextMatchesAreHighlighted())
      return;
  
    const auto paint_offsets =
        GetTextMatchMarkerPaintOffsets(marker, inline_text_box_);
    TextRun run = inline_text_box_.ConstructTextRun(style);
  
    Color color = LayoutTheme::GetTheme().PlatformTextSearchHighlightColor(
        marker.IsActiveMatch(), style.UsedColorScheme());
    GraphicsContext& context = paint_info.context;
    GraphicsContextStateSaver state_saver(context);
  
    // If vertical, |box_rect| is in the physical coordinates space under the
    // rotation transform.
    PhysicalRect box_rect(box_origin,
                          PhysicalSize(inline_text_box_.LogicalWidth(),
                                       inline_text_box_.LogicalHeight()));
    context.Clip(FloatRect(box_rect));
    context.DrawHighlightForText(font, run, FloatPoint(box_origin),
                                 box_rect.Height().ToInt(), color,
                                 paint_offsets.first, paint_offsets.second);
  }
  
  }  // namespace blink
  