| // 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 "core/paint/InlineTextBoxPainter.h" |
| |
| #include "core/editing/Editor.h" |
| #include "core/editing/markers/CompositionMarker.h" |
| #include "core/editing/markers/DocumentMarkerController.h" |
| #include "core/editing/markers/TextMatchMarker.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/layout/LayoutTextCombine.h" |
| #include "core/layout/LayoutTheme.h" |
| #include "core/layout/api/LineLayoutAPIShim.h" |
| #include "core/layout/api/LineLayoutBox.h" |
| #include "core/layout/line/InlineTextBox.h" |
| #include "core/paint/AppliedDecorationPainter.h" |
| #include "core/paint/DecorationInfo.h" |
| #include "core/paint/PaintInfo.h" |
| #include "core/paint/TextPainter.h" |
| #include "platform/graphics/GraphicsContextStateSaver.h" |
| #include "platform/graphics/paint/DrawingRecorder.h" |
| #include "platform/graphics/paint/PaintRecord.h" |
| #include "platform/graphics/paint/PaintRecorder.h" |
| #include "platform/graphics/paint/PaintShader.h" |
| #include "platform/wtf/Optional.h" |
| #include "third_party/skia/include/effects/SkGradientShader.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| std::pair<unsigned, unsigned> GetMarkerPaintOffsets( |
| const DocumentMarker& marker, |
| const InlineTextBox& text_box) { |
| 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); |
| } |
| |
| } // 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 (isHTMLAnchorElement(node) || node->HasTagName(HTMLNames::fontTag)) |
| return current; |
| } |
| } |
| } |
| |
| static int ComputeUnderlineOffsetForUnder( |
| const ComputedStyle& style, |
| const InlineTextBox* inline_text_box, |
| LineLayoutItem decorating_box, |
| float text_decoration_thickness, |
| LineVerticalPositionType position_type) { |
| const RootInlineBox& root = inline_text_box->Root(); |
| FontBaseline baseline_type = root.BaselineType(); |
| LayoutUnit offset = inline_text_box->OffsetTo(position_type, baseline_type); |
| |
| // Compute offset to the farthest position of the decorating box. |
| LayoutUnit logical_top = inline_text_box->LogicalTop(); |
| LayoutUnit position = logical_top + offset; |
| LayoutUnit farthest = root.FarthestPositionForUnderline( |
| decorating_box, position_type, baseline_type, position); |
| // Round() looks more logical but Floor() produces better results in |
| // positive/negative offsets, in horizontal/vertical flows, on Win/Mac/Linux. |
| int offset_int = (farthest - logical_top).Floor(); |
| |
| // Gaps are not needed for TextTop because it generally has internal |
| // leadings. |
| if (position_type == LineVerticalPositionType::TextTop) |
| return offset_int; |
| return !IsLineOverSide(position_type) ? offset_int + 1 : offset_int - 1; |
| } |
| |
| static int ComputeUnderlineOffsetForRoman( |
| const FontMetrics& font_metrics, |
| const float text_decoration_thickness) { |
| // Compute the gap between the font and the underline. Use at least one |
| // pixel gap, if underline is thick then use a bigger gap. |
| int gap = 0; |
| |
| // Underline position of zero means draw underline on Baseline Position, |
| // in Blink we need at least 1-pixel gap to adding following check. |
| // Positive underline Position means underline should be drawn above baseline |
| // and negative value means drawing below baseline, negating the value as in |
| // Blink downward Y-increases. |
| |
| if (font_metrics.UnderlinePosition()) |
| gap = -font_metrics.UnderlinePosition(); |
| else |
| gap = std::max<int>(1, ceilf(text_decoration_thickness / 2.f)); |
| |
| // Position underline near the alphabetic baseline. |
| return font_metrics.Ascent() + gap; |
| } |
| |
| static int ComputeUnderlineOffset(ResolvedUnderlinePosition underline_position, |
| const ComputedStyle& style, |
| const FontMetrics& font_metrics, |
| const InlineTextBox* inline_text_box, |
| LineLayoutItem decorating_box, |
| const float text_decoration_thickness) { |
| switch (underline_position) { |
| default: |
| NOTREACHED(); |
| // Fall through. |
| case ResolvedUnderlinePosition::kRoman: |
| return ComputeUnderlineOffsetForRoman(font_metrics, |
| text_decoration_thickness); |
| case ResolvedUnderlinePosition::kUnder: |
| // Position underline at the under edge of the lowest element's |
| // content box. |
| return ComputeUnderlineOffsetForUnder( |
| style, inline_text_box, decorating_box, text_decoration_thickness, |
| LineVerticalPositionType::BottomOfEmHeight); |
| } |
| } |
| |
| static const int kMisspellingLineThickness = 3; |
| |
| LayoutObject& InlineTextBoxPainter::InlineLayoutObject() const { |
| return *LineLayoutAPIShim::LayoutObjectFrom( |
| inline_text_box_.GetLineLayoutItem()); |
| } |
| |
| bool InlineTextBoxPainter::PaintsMarkerHighlights( |
| const LayoutObject& layout_object) { |
| return layout_object.GetNode() && |
| layout_object.GetDocument().Markers().HasMarkers( |
| layout_object.GetNode()); |
| } |
| |
| static void PrepareContextForDecoration( |
| GraphicsContext& context, |
| GraphicsContextStateSaver& state_saver, |
| bool is_horizontal, |
| const TextPainterBase::Style& text_style, |
| const LayoutTextCombine* combined_text, |
| const LayoutRect& box_rect) { |
| TextPainterBase::UpdateGraphicsContext(context, text_style, is_horizontal, |
| state_saver); |
| if (combined_text) { |
| context.ConcatCTM( |
| TextPainterBase::Rotation(box_rect, TextPainterBase::kClockwise)); |
| } |
| } |
| |
| static void RestoreContextFromDecoration(GraphicsContext& context, |
| const LayoutTextCombine* combined_text, |
| const LayoutRect& box_rect) { |
| if (combined_text) { |
| context.ConcatCTM(TextPainterBase::Rotation( |
| box_rect, TextPainterBase::kCounterclockwise)); |
| } |
| } |
| |
| |
| static void ComputeOriginAndWidthForBox(const InlineTextBox& box, |
| LayoutPoint& local_origin, |
| LayoutUnit& width) { |
| if (box.Truncation() != kCNoTruncation) { |
| bool ltr = box.IsLeftToRightDirection(); |
| bool flow_is_ltr = |
| box.GetLineLayoutItem().Style()->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.Move(box.LogicalWidth() - width, LayoutUnit()); |
| } |
| } |
| } |
| |
| static void PaintDecorationsExceptLineThrough( |
| TextPainter& text_painter, |
| bool& has_line_through_decoration, |
| const InlineTextBox& box, |
| const DecorationInfo& decoration_info, |
| const LineLayoutItem& decorating_box, |
| const PaintInfo& paint_info, |
| const Vector<AppliedTextDecoration>& decorations) { |
| GraphicsContext& context = paint_info.context; |
| GraphicsContextStateSaver state_saver(context); |
| context.SetStrokeThickness(decoration_info.thickness); |
| |
| // text-underline-position may flip underline and overline. |
| ResolvedUnderlinePosition underline_position = |
| decoration_info.underline_position; |
| bool flip_underline_and_overline = false; |
| if (underline_position == ResolvedUnderlinePosition::kOver) { |
| flip_underline_and_overline = true; |
| underline_position = ResolvedUnderlinePosition::kUnder; |
| } |
| |
| for (const AppliedTextDecoration& decoration : decorations) { |
| TextDecoration lines = decoration.Lines(); |
| if (flip_underline_and_overline) { |
| lines ^= (TextDecoration::kUnderline | TextDecoration::kOverline); |
| } |
| if (EnumHasFlags(lines, TextDecoration::kUnderline) && |
| decoration_info.font_data) { |
| const int underline_offset = ComputeUnderlineOffset( |
| underline_position, *decoration_info.style, |
| decoration_info.font_data->GetFontMetrics(), &box, decorating_box, |
| decoration_info.thickness); |
| text_painter.PaintDecorationUnderOrOverLine( |
| context, decoration_info, decoration, underline_offset, |
| decoration_info.double_offset); |
| } |
| if (EnumHasFlags(lines, TextDecoration::kOverline)) { |
| const int overline_offset = ComputeUnderlineOffsetForUnder( |
| *decoration_info.style, &box, decorating_box, |
| decoration_info.thickness, |
| flip_underline_and_overline ? LineVerticalPositionType::TopOfEmHeight |
| : LineVerticalPositionType::TextTop); |
| text_painter.PaintDecorationUnderOrOverLine( |
| context, decoration_info, decoration, overline_offset, |
| -decoration_info.double_offset); |
| } |
| // We could instead build a vector of the TextDecoration instances needing |
| // line-through but this is a rare case so better to avoid vector overhead. |
| has_line_through_decoration |= |
| EnumHasFlags(lines, TextDecoration::kLineThrough); |
| } |
| } |
| |
| void InlineTextBoxPainter::Paint(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| if (!ShouldPaintTextBox(paint_info)) |
| return; |
| |
| DCHECK(!ShouldPaintSelfOutline(paint_info.phase) && |
| !ShouldPaintDescendantOutlines(paint_info.phase)); |
| |
| LayoutRect logical_visual_overflow = inline_text_box_.LogicalOverflowRect(); |
| LayoutUnit logical_start = |
| logical_visual_overflow.X() + |
| (inline_text_box_.IsHorizontal() ? paint_offset.X() : paint_offset.Y()); |
| LayoutUnit logical_extent = logical_visual_overflow.Width(); |
| |
| // We round the y-axis to ensure consistent line heights. |
| LayoutPoint adjusted_paint_offset = |
| LayoutPoint(paint_offset.X(), LayoutUnit(paint_offset.Y().Round())); |
| |
| if (inline_text_box_.IsHorizontal()) { |
| if (!paint_info.GetCullRect().IntersectsHorizontalRange( |
| logical_start, logical_start + logical_extent)) |
| return; |
| } else { |
| if (!paint_info.GetCullRect().IntersectsVerticalRange( |
| logical_start, logical_start + logical_extent)) |
| return; |
| } |
| |
| bool is_printing = paint_info.IsPrinting(); |
| |
| // Determine whether or not we're selected. |
| bool have_selection = |
| !is_printing && paint_info.phase != kPaintPhaseTextClip && |
| inline_text_box_.GetSelectionState() != SelectionState::kNone; |
| if (!have_selection && paint_info.phase == kPaintPhaseSelection) { |
| // When only painting the selection, don't bother to paint if there is none. |
| return; |
| } |
| |
| // The text clip phase already has a LayoutObjectDrawingRecorder. Text clips |
| // are initiated only in BoxPainter::paintFillLayer, which is already within a |
| // LayoutObjectDrawingRecorder. |
| Optional<DrawingRecorder> drawing_recorder; |
| if (paint_info.phase != kPaintPhaseTextClip) { |
| if (DrawingRecorder::UseCachedDrawingIfPossible( |
| paint_info.context, inline_text_box_, |
| DisplayItem::PaintPhaseToDrawingType(paint_info.phase))) |
| return; |
| LayoutRect paint_rect(logical_visual_overflow); |
| inline_text_box_.LogicalRectToPhysicalRect(paint_rect); |
| if (paint_info.phase != kPaintPhaseSelection && |
| (have_selection || PaintsMarkerHighlights(InlineLayoutObject()))) |
| paint_rect.Unite(inline_text_box_.LocalSelectionRect( |
| inline_text_box_.Start(), |
| inline_text_box_.Start() + inline_text_box_.Len())); |
| paint_rect.MoveBy(adjusted_paint_offset); |
| drawing_recorder.emplace( |
| paint_info.context, inline_text_box_, |
| DisplayItem::PaintPhaseToDrawingType(paint_info.phase), |
| FloatRect(paint_rect)); |
| } |
| |
| GraphicsContext& context = paint_info.context; |
| const ComputedStyle& style_to_use = |
| inline_text_box_.GetLineLayoutItem().StyleRef( |
| inline_text_box_.IsFirstLineStyle()); |
| |
| LayoutPoint box_origin(inline_text_box_.PhysicalLocation()); |
| box_origin.Move(adjusted_paint_offset.X(), adjusted_paint_offset.Y()); |
| LayoutRect box_rect(box_origin, LayoutSize(inline_text_box_.LogicalWidth(), |
| inline_text_box_.LogicalHeight())); |
| |
| int length = inline_text_box_.Len(); |
| StringView string = StringView(inline_text_box_.GetLineLayoutItem().GetText(), |
| 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 : 0); |
| if (inline_text_box_.HasHyphen()) |
| length = text_run.length(); |
| |
| bool should_rotate = false; |
| LayoutTextCombine* combined_text = nullptr; |
| if (!inline_text_box_.IsHorizontal()) { |
| if (style_to_use.HasTextCombine() && |
| inline_text_box_.GetLineLayoutItem().IsCombineText()) { |
| combined_text = &ToLayoutTextCombine(InlineLayoutObject()); |
| if (!combined_text->IsCombined()) |
| combined_text = nullptr; |
| } |
| if (combined_text) { |
| combined_text->UpdateFont(); |
| 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; |
| LayoutSize offset = |
| LayoutSize(LayoutUnit(), LayoutUnit::FromFloatRound(expansion)); |
| box_origin.Move(offset); |
| box_rect.Move(offset); |
| } |
| } |
| } else { |
| should_rotate = true; |
| context.ConcatCTM( |
| TextPainterBase::Rotation(box_rect, TextPainterBase::kClockwise)); |
| } |
| } |
| |
| // Determine text colors. |
| TextPainterBase::Style text_style = TextPainterBase::TextPaintingStyle( |
| inline_text_box_.GetLineLayoutItem().GetDocument(), style_to_use, |
| paint_info); |
| TextPainterBase::Style selection_style = TextPainter::SelectionPaintingStyle( |
| inline_text_box_.GetLineLayoutItem(), have_selection, paint_info, |
| text_style); |
| bool paint_selected_text_only = (paint_info.phase == kPaintPhaseSelection); |
| 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; |
| LayoutPoint text_origin(box_origin.X(), box_origin.Y() + ascent); |
| |
| // 1. Paint backgrounds behind text if needed. Examples of such backgrounds |
| // include selection and composition highlights. |
| if (paint_info.phase != kPaintPhaseSelection && |
| paint_info.phase != kPaintPhaseTextClip && !is_printing) { |
| PaintDocumentMarkers(paint_info, box_origin, style_to_use, font, |
| DocumentMarkerPaintPhase::kBackground); |
| if (have_selection) { |
| if (combined_text) |
| PaintSelection<InlineTextBoxPainter::PaintOptions::kCombinedText>( |
| context, box_rect, style_to_use, font, selection_style.fill_color, |
| combined_text); |
| else |
| PaintSelection<InlineTextBoxPainter::PaintOptions::kNormal>( |
| context, box_rect, style_to_use, font, selection_style.fill_color); |
| } |
| } |
| |
| // 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() |
| .Style() |
| ->IsLeftToRightDirection(); |
| 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. |
| selection_start = |
| ltr == flow_is_ltr |
| ? std::min<int>(selection_start, inline_text_box_.Truncation()) |
| : std::max<int>(selection_start, inline_text_box_.Truncation()); |
| selection_end = |
| ltr == flow_is_ltr |
| ? std::min<int>(selection_end, inline_text_box_.Truncation()) |
| : std::max<int>(selection_end, inline_text_box_.Truncation()); |
| 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()); |
| |
| if (!paint_selected_text_only) { |
| // Paint text decorations except line-through. |
| DecorationInfo decoration_info; |
| bool has_line_through_decoration = false; |
| if (style_to_use.TextDecorationsInEffect() != TextDecoration::kNone && |
| inline_text_box_.Truncation() != kCFullTruncation) { |
| LayoutPoint local_origin = LayoutPoint(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; |
| text_painter.ComputeDecorationInfo(decoration_info, box_origin, |
| local_origin, width, |
| inline_text_box_.Root().BaselineType(), |
| style_to_use, decorating_box_style); |
| GraphicsContextStateSaver state_saver(context, false); |
| PrepareContextForDecoration(context, state_saver, |
| inline_text_box_.IsHorizontal(), text_style, |
| combined_text, box_rect); |
| PaintDecorationsExceptLineThrough( |
| text_painter, has_line_through_decoration, inline_text_box_, |
| decoration_info, decorating_box, paint_info, |
| style_to_use.AppliedTextDecorations()); |
| RestoreContextFromDecoration(context, combined_text, box_rect); |
| } |
| |
| 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); |
| |
| // Paint line-through decoration if needed. |
| if (has_line_through_decoration) { |
| GraphicsContextStateSaver state_saver(context, false); |
| PrepareContextForDecoration(context, state_saver, |
| inline_text_box_.IsHorizontal(), text_style, |
| combined_text, box_rect); |
| text_painter.PaintDecorationsOnlyLineThrough( |
| decoration_info, paint_info, style_to_use.AppliedTextDecorations()); |
| RestoreContextFromDecoration(context, combined_text, box_rect); |
| } |
| } |
| |
| if ((paint_selected_text_only || paint_selected_text_separately) && |
| selection_start < selection_end) { |
| // paint only the text that is selected |
| text_painter.Paint(selection_start, selection_end, length, selection_style); |
| } |
| |
| if (paint_info.phase == kPaintPhaseForeground) |
| PaintDocumentMarkers(paint_info, box_origin, style_to_use, font, |
| DocumentMarkerPaintPhase::kForeground); |
| |
| if (should_rotate) { |
| context.ConcatCTM(TextPainterBase::Rotation( |
| box_rect, TextPainterBase::kCounterclockwise)); |
| } |
| } |
| |
| bool InlineTextBoxPainter::ShouldPaintTextBox(const PaintInfo& paint_info) { |
| // When painting selection, we want to include a highlight when the |
| // selection spans line breaks. In other cases such as invisible elements |
| // or those with no text that are not line breaks, we can skip painting |
| // wholesale. |
| // TODO(wkorman): Constrain line break painting to appropriate paint phase. |
| // This code path is only called in PaintPhaseForeground whereas we would |
| // expect PaintPhaseSelection. The existing haveSelection logic in paint() |
| // tests for != PaintPhaseTextClip. |
| if (inline_text_box_.GetLineLayoutItem().Style()->Visibility() != |
| EVisibility::kVisible || |
| inline_text_box_.Truncation() == kCFullTruncation || |
| !inline_text_box_.Len()) |
| return false; |
| return true; |
| } |
| |
| unsigned InlineTextBoxPainter::MarkerPaintStart(const DocumentMarker& marker) { |
| DCHECK(inline_text_box_.Truncation() != kCFullTruncation); |
| DCHECK(inline_text_box_.Len()); |
| |
| // Start painting at the beginning of the text or the specified underline |
| // start offset, whichever is higher. |
| unsigned paint_start = |
| std::max(inline_text_box_.Start(), marker.StartOffset()); |
| // Cap the maximum paint start to (if no truncation) the last character, |
| // else the last character before the truncation ellipsis. |
| return std::min(paint_start, (inline_text_box_.Truncation() == kCNoTruncation) |
| ? inline_text_box_.end() |
| : inline_text_box_.Start() + |
| inline_text_box_.Truncation() - 1); |
| } |
| |
| unsigned InlineTextBoxPainter::MarkerPaintEnd(const DocumentMarker& marker) { |
| DCHECK(inline_text_box_.Truncation() != kCFullTruncation); |
| DCHECK(inline_text_box_.Len()); |
| |
| // End painting just past the end of the text or the specified underline end |
| // offset, whichever is lower. |
| unsigned paint_end = std::min( |
| inline_text_box_.end() + 1, |
| marker.EndOffset()); // end() points at the last char, not past it. |
| // Cap the maximum paint end to (if no truncation) one past the last |
| // character, else one past the last character before the truncation |
| // ellipsis. |
| return std::min(paint_end, (inline_text_box_.Truncation() == kCNoTruncation) |
| ? inline_text_box_.end() + 1 |
| : inline_text_box_.Start() + |
| inline_text_box_.Truncation()); |
| } |
| |
| void InlineTextBoxPainter::PaintSingleMarkerBackgroundRun( |
| GraphicsContext& context, |
| const LayoutPoint& box_origin, |
| const ComputedStyle& style, |
| const Font& font, |
| Color background_color, |
| int start_pos, |
| int end_pos) { |
| if (background_color == Color::kTransparent) |
| return; |
| |
| int s_pos = |
| std::max(start_pos - static_cast<int>(inline_text_box_.Start()), 0); |
| int e_pos = std::min(end_pos - static_cast<int>(inline_text_box_.Start()), |
| static_cast<int>(inline_text_box_.Len())); |
| if (s_pos >= e_pos) |
| return; |
| |
| int delta_y = |
| (inline_text_box_.GetLineLayoutItem().Style()->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.X().ToFloat(), |
| box_origin.Y().ToFloat() - delta_y); |
| context.DrawHighlightForText(font, inline_text_box_.ConstructTextRun(style), |
| local_origin, sel_height, background_color, |
| s_pos, e_pos); |
| } |
| |
| void InlineTextBoxPainter::PaintDocumentMarkers( |
| const PaintInfo& paint_info, |
| const LayoutPoint& 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 markers = |
| inline_text_box_.GetLineLayoutItem().GetDocument().Markers().MarkersFor( |
| inline_text_box_.GetLineLayoutItem().GetNode()); |
| DocumentMarkerVector::const_iterator marker_it = markers.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.end(); ++marker_it) { |
| DCHECK(*marker_it); |
| const DocumentMarker& marker = **marker_it; |
| |
| // Paint either the background markers or the foreground markers, but not |
| // both. |
| switch (marker.GetType()) { |
| case DocumentMarker::kGrammar: |
| case DocumentMarker::kSpelling: |
| if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground) |
| continue; |
| break; |
| case DocumentMarker::kTextMatch: |
| case DocumentMarker::kComposition: |
| case DocumentMarker::kActiveSuggestion: |
| break; |
| } |
| |
| 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. |
| break; |
| } |
| |
| // marker intersects this run. Paint it. |
| switch (marker.GetType()) { |
| case DocumentMarker::kSpelling: |
| inline_text_box_.PaintDocumentMarker(paint_info.context, box_origin, |
| marker, style, font, false); |
| break; |
| case DocumentMarker::kGrammar: |
| inline_text_box_.PaintDocumentMarker(paint_info.context, box_origin, |
| marker, style, font, true); |
| break; |
| case DocumentMarker::kTextMatch: |
| if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground) { |
| inline_text_box_.PaintTextMatchMarkerBackground( |
| paint_info, box_origin, ToTextMatchMarker(marker), style, font); |
| } else { |
| inline_text_box_.PaintTextMatchMarkerForeground( |
| paint_info, box_origin, ToTextMatchMarker(marker), style, font); |
| } |
| break; |
| case DocumentMarker::kComposition: |
| case DocumentMarker::kActiveSuggestion: { |
| const StyleableMarker& styleable_marker = ToStyleableMarker(marker); |
| if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground) { |
| PaintSingleMarkerBackgroundRun(paint_info.context, box_origin, style, |
| font, |
| styleable_marker.BackgroundColor(), |
| MarkerPaintStart(styleable_marker), |
| MarkerPaintEnd(styleable_marker)); |
| } else { |
| PaintStyleableMarkerUnderline(paint_info.context, box_origin, |
| styleable_marker); |
| } |
| } break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| } |
| |
| namespace { |
| |
| #if !OS(MACOSX) |
| |
| static const float kMarkerWidth = 4; |
| static const float kMarkerHeight = 2; |
| |
| sk_sp<PaintRecord> RecordMarker(DocumentMarker::MarkerType marker_type) { |
| SkColor color = |
| (marker_type == DocumentMarker::kGrammar) |
| ? LayoutTheme::GetTheme().PlatformGrammarMarkerUnderlineColor().Rgb() |
| : LayoutTheme::GetTheme() |
| .PlatformSpellingMarkerUnderlineColor() |
| .Rgb(); |
| |
| // Record the path equivalent to this legacy pattern: |
| // X o o X o o X |
| // o X o o X o |
| |
| // Adjust the phase such that f' == 0 is "pixel"-centered |
| // (for optimal rasterization at native rez). |
| SkPath path; |
| path.moveTo(kMarkerWidth * -3 / 8, kMarkerHeight * 3 / 4); |
| path.cubicTo(kMarkerWidth * -1 / 8, kMarkerHeight * 3 / 4, |
| kMarkerWidth * -1 / 8, kMarkerHeight * 1 / 4, |
| kMarkerWidth * 1 / 8, kMarkerHeight * 1 / 4); |
| path.cubicTo(kMarkerWidth * 3 / 8, kMarkerHeight * 1 / 4, |
| kMarkerWidth * 3 / 8, kMarkerHeight * 3 / 4, |
| kMarkerWidth * 5 / 8, kMarkerHeight * 3 / 4); |
| path.cubicTo(kMarkerWidth * 7 / 8, kMarkerHeight * 3 / 4, |
| kMarkerWidth * 7 / 8, kMarkerHeight * 1 / 4, |
| kMarkerWidth * 9 / 8, kMarkerHeight * 1 / 4); |
| |
| PaintFlags flags; |
| flags.setAntiAlias(true); |
| flags.setColor(color); |
| flags.setStyle(PaintFlags::kStroke_Style); |
| flags.setStrokeWidth(kMarkerHeight * 1 / 2); |
| |
| PaintRecorder recorder; |
| recorder.beginRecording(kMarkerWidth, kMarkerHeight); |
| recorder.getRecordingCanvas()->drawPath(path, flags); |
| |
| return recorder.finishRecordingAsPicture(); |
| } |
| |
| #else // OS(MACOSX) |
| |
| static const float kMarkerWidth = 4; |
| static const float kMarkerHeight = 3; |
| |
| sk_sp<PaintRecord> RecordMarker(DocumentMarker::MarkerType marker_type) { |
| SkColor color = |
| (marker_type == DocumentMarker::kGrammar) |
| ? LayoutTheme::GetTheme().PlatformGrammarMarkerUnderlineColor().Rgb() |
| : LayoutTheme::GetTheme() |
| .PlatformSpellingMarkerUnderlineColor() |
| .Rgb(); |
| |
| // Match the artwork used by the Mac. |
| static const float kR = 1.5f; |
| |
| // top->bottom translucent gradient. |
| const SkColor colors[2] = { |
| SkColorSetARGB(0x48, |
| SkColorGetR(color), |
| SkColorGetG(color), |
| SkColorGetB(color)), |
| color |
| }; |
| const SkPoint pts[2] = { |
| SkPoint::Make(0, 0), |
| SkPoint::Make(0, 2 * kR) |
| }; |
| |
| PaintFlags flags; |
| flags.setAntiAlias(true); |
| flags.setColor(color); |
| flags.setShader(PaintShader::MakeLinearGradient( |
| pts, colors, nullptr, ARRAY_SIZE(colors), SkShader::kClamp_TileMode)); |
| PaintRecorder recorder; |
| recorder.beginRecording(kMarkerWidth, kMarkerHeight); |
| recorder.getRecordingCanvas()->drawCircle(kR, kR, kR, flags); |
| |
| return recorder.finishRecordingAsPicture(); |
| } |
| |
| #endif // OS(MACOSX) |
| |
| void DrawDocumentMarker(GraphicsContext& context, |
| const FloatPoint& pt, |
| float width, |
| DocumentMarker::MarkerType marker_type, |
| float zoom) { |
| DCHECK(marker_type == DocumentMarker::kSpelling || |
| marker_type == DocumentMarker::kGrammar); |
| |
| DEFINE_STATIC_LOCAL(PaintRecord*, spelling_marker, |
| (RecordMarker(DocumentMarker::kSpelling).release())); |
| DEFINE_STATIC_LOCAL(PaintRecord*, grammar_marker, |
| (RecordMarker(DocumentMarker::kGrammar).release())); |
| const auto& marker = marker_type == DocumentMarker::kSpelling |
| ? spelling_marker |
| : grammar_marker; |
| |
| // Position already includes zoom and device scale factor. |
| SkScalar origin_x = WebCoreFloatToSkScalar(pt.X()); |
| SkScalar origin_y = WebCoreFloatToSkScalar(pt.Y()); |
| |
| #if OS(MACOSX) |
| // Make sure to draw only complete dots, and finish inside the marked text. |
| width -= fmodf(width, kMarkerWidth * zoom); |
| #else |
| // Offset it vertically by 1 so that there's some space under the text. |
| origin_y += 1; |
| #endif |
| |
| const auto rect = SkRect::MakeWH(width, kMarkerHeight * zoom); |
| const auto local_matrix = SkMatrix::MakeScale(zoom, zoom); |
| |
| PaintFlags flags; |
| flags.setAntiAlias(true); |
| flags.setShader(PaintShader::MakePaintRecord( |
| sk_ref_sp(marker), FloatRect(0, 0, kMarkerWidth, kMarkerHeight), |
| SkShader::kRepeat_TileMode, SkShader::kClamp_TileMode, &local_matrix)); |
| |
| // Apply the origin translation as a global transform. This ensures that the |
| // shader local matrix depends solely on zoom => Skia can reuse the same |
| // cached tile for all markers at a given zoom level. |
| GraphicsContextStateSaver saver(context); |
| context.Translate(origin_x, origin_y); |
| context.DrawRect(rect, flags); |
| } |
| |
| } // anonymous ns |
| |
| void InlineTextBoxPainter::PaintDocumentMarker(GraphicsContext& context, |
| const LayoutPoint& box_origin, |
| const DocumentMarker& marker, |
| const ComputedStyle& style, |
| const Font& font, |
| bool grammar) { |
| // Never print spelling/grammar markers (5327887) |
| 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) { |
| int start_position, end_position; |
| std::tie(start_position, end_position) = |
| GetMarkerPaintOffsets(marker, inline_text_box_); |
| |
| if (inline_text_box_.Truncation() != kCNoTruncation) |
| end_position = std::min<int>(end_position, inline_text_box_.Truncation()); |
| |
| // Calculate start & width |
| int delta_y = (inline_text_box_.GetLineLayoutItem() |
| .Style() |
| ->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(); |
| LayoutPoint start_point(box_origin.X(), box_origin.Y() - 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, |
| start_position, end_position)); |
| start = marker_rect.X() - start_point.X(); |
| width = LayoutUnit(marker_rect.Width()); |
| } |
| |
| // IMPORTANT: The misspelling underline is not considered when calculating the |
| // text bounds, so we have to make sure to fit within those bounds. This |
| // means the top pixel(s) of the underline will overlap the bottom pixel(s) of |
| // the glyphs in smaller font sizes. The alternatives are to increase the |
| // line spacing (bad!!) or decrease the underline thickness. The overlap is |
| // actually the most useful, and matches what AppKit does. So, we generally |
| // place the underline at the bottom of the text, but in larger fonts that's |
| // not so good so we pin to two pixels under the baseline. |
| int line_thickness = kMisspellingLineThickness; |
| |
| const SimpleFontData* font_data = |
| inline_text_box_.GetLineLayoutItem() |
| .Style(inline_text_box_.IsFirstLineStyle()) |
| ->GetFont() |
| .PrimaryFont(); |
| DCHECK(font_data); |
| int baseline = font_data ? font_data->GetFontMetrics().Ascent() : 0; |
| int descent = (inline_text_box_.LogicalHeight() - baseline).ToInt(); |
| int underline_offset; |
| if (descent <= (line_thickness + 2)) { |
| // Place the underline at the very bottom of the text in small/medium fonts. |
| underline_offset = |
| (inline_text_box_.LogicalHeight() - line_thickness).ToInt(); |
| } else { |
| // In larger fonts, though, place the underline up near the baseline to |
| // prevent a big gap. |
| underline_offset = baseline + 2; |
| } |
| DrawDocumentMarker(context, |
| FloatPoint((box_origin.X() + start).ToFloat(), |
| (box_origin.Y() + underline_offset).ToFloat()), |
| width.ToFloat(), marker.GetType(), style.EffectiveZoom()); |
| } |
| |
| template <InlineTextBoxPainter::PaintOptions options> |
| void InlineTextBoxPainter::PaintSelection(GraphicsContext& context, |
| const LayoutRect& box_rect, |
| const ComputedStyle& style, |
| const Font& font, |
| Color text_color, |
| LayoutTextCombine* combined_text) { |
| // See if we have a selection to paint at all. |
| int s_pos, e_pos; |
| inline_text_box_.SelectionStartEnd(s_pos, e_pos); |
| if (s_pos >= e_pos) |
| return; |
| |
| Color c = inline_text_box_.GetLineLayoutItem().SelectionBackgroundColor(); |
| if (!c.Alpha()) |
| return; |
| |
| // If the text color ends up being the same as the selection background, |
| // invert the selection background. |
| if (text_color == c) |
| c = Color(0xff - c.Red(), 0xff - c.Green(), 0xff - c.Blue()); |
| |
| // 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() |
| .Style() |
| ->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) |
| s_pos = std::max<int>(s_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 = e_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 : 0); |
| if (respect_hyphen) |
| e_pos = text_run.length(); |
| |
| GraphicsContextStateSaver state_saver(context); |
| |
| 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); |
| context.DrawHighlightForText(font, text_run, |
| FloatPoint(box_rect.Location()), |
| box_rect.Height().ToInt(), c, s_pos, e_pos); |
| return; |
| } |
| |
| LayoutUnit selection_bottom = inline_text_box_.Root().SelectionBottom(); |
| LayoutUnit selection_top = inline_text_box_.Root().SelectionTop(); |
| |
| int delta_y = RoundToInt( |
| inline_text_box_.GetLineLayoutItem().Style()->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()); |
| LayoutRect selection_rect = LayoutRect(font.SelectionRectForText( |
| text_run, local_origin, sel_height, s_pos, e_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(-selection_rect.Width(), LayoutUnit()); |
| if (!flow_is_ltr && !ltr && inline_text_box_.Truncation() != kCNoTruncation) |
| selection_rect.Move( |
| inline_text_box_.LogicalWidth() - selection_rect.Width(), LayoutUnit()); |
| |
| context.FillRect(FloatRect(selection_rect), c); |
| } |
| |
| void InlineTextBoxPainter::ExpandToIncludeNewlineForSelection( |
| LayoutRect& 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 LayoutPoint& box_origin, |
| const StyleableMarker& marker) { |
| if (marker.UnderlineColor() == Color::kTransparent) |
| return; |
| |
| if (inline_text_box_.Truncation() == kCFullTruncation) |
| return; |
| |
| unsigned paint_start = MarkerPaintStart(marker); |
| unsigned paint_end = MarkerPaintEnd(marker); |
| DCHECK_LT(paint_start, paint_end); |
| |
| // start of line to draw |
| float start = |
| paint_start == inline_text_box_.Start() |
| ? 0 |
| : inline_text_box_.GetLineLayoutItem().Width( |
| inline_text_box_.Start(), |
| paint_start - inline_text_box_.Start(), |
| inline_text_box_.TextPos(), |
| inline_text_box_.IsLeftToRightDirection() ? TextDirection::kLtr |
| : TextDirection::kRtl, |
| inline_text_box_.IsFirstLineStyle()); |
| // how much line to draw |
| float width; |
| bool ltr = inline_text_box_.IsLeftToRightDirection(); |
| bool flow_is_ltr = |
| inline_text_box_.GetLineLayoutItem().Style()->IsLeftToRightDirection(); |
| if (paint_start == inline_text_box_.Start() && |
| paint_end == inline_text_box_.end() + 1) { |
| width = inline_text_box_.LogicalWidth().ToFloat(); |
| } else { |
| unsigned paint_from = ltr == flow_is_ltr ? paint_start : paint_end; |
| unsigned paint_length = |
| ltr == flow_is_ltr |
| ? paint_end - paint_start |
| : inline_text_box_.Start() + inline_text_box_.Len() - paint_end; |
| width = inline_text_box_.GetLineLayoutItem().Width( |
| paint_from, paint_length, |
| LayoutUnit(inline_text_box_.TextPos() + start), |
| flow_is_ltr ? TextDirection::kLtr : TextDirection::kRtl, |
| inline_text_box_.IsFirstLineStyle()); |
| } |
| // In RTL mode, start and width are computed from the right end of the text |
| // box: starting at |logicalWidth| - |start| and continuing left by |width| to |
| // |logicalWidth| - |start| - |width|. We will draw that line, but backwards: |
| // |logicalWidth| - |start| - |width| to |logicalWidth| - |start|. |
| if (!flow_is_ltr) |
| start = inline_text_box_.LogicalWidth().ToFloat() - width - start; |
| |
| // Thick marked text underlines are 2px thick as long as there is room for the |
| // 2px line under the baseline. All other marked text underlines are 1px |
| // thick. If there's not enough space the underline will touch or overlap |
| // characters. |
| int line_thickness = 1; |
| const SimpleFontData* font_data = |
| inline_text_box_.GetLineLayoutItem() |
| .Style(inline_text_box_.IsFirstLineStyle()) |
| ->GetFont() |
| .PrimaryFont(); |
| DCHECK(font_data); |
| int baseline = font_data ? font_data->GetFontMetrics().Ascent() : 0; |
| if (marker.IsThick() && inline_text_box_.LogicalHeight() - baseline >= 2) |
| line_thickness = 2; |
| |
| // We need to have some space between underlines of subsequent clauses, |
| // because some input methods do not use different underline styles for those. |
| // We make each line shorter, which has a harmless side effect of shortening |
| // the first and last clauses, too. |
| start += 1; |
| width -= 2; |
| |
| context.SetStrokeColor(marker.UnderlineColor()); |
| context.SetStrokeThickness(line_thickness); |
| context.DrawLineForText( |
| FloatPoint( |
| box_origin.X() + start, |
| (box_origin.Y() + inline_text_box_.LogicalHeight() - line_thickness) |
| .ToFloat()), |
| width); |
| } |
| |
| void InlineTextBoxPainter::PaintTextMatchMarkerForeground( |
| const PaintInfo& paint_info, |
| const LayoutPoint& box_origin, |
| const TextMatchMarker& marker, |
| const ComputedStyle& style, |
| const Font& font) { |
| if (!InlineLayoutObject() |
| .GetFrame() |
| ->GetEditor() |
| .MarkedTextMatchesAreHighlighted()) |
| return; |
| |
| const auto paint_offsets = GetMarkerPaintOffsets(marker, inline_text_box_); |
| TextRun run = inline_text_box_.ConstructTextRun(style); |
| |
| Color text_color = |
| LayoutTheme::GetTheme().PlatformTextSearchColor(marker.IsActiveMatch()); |
| if (style.VisitedDependentColor(CSSPropertyColor) == text_color) |
| return; |
| |
| const SimpleFontData* font_data = font.PrimaryFont(); |
| DCHECK(font_data); |
| if (!font_data) |
| return; |
| |
| TextPainterBase::Style text_style; |
| text_style.current_color = text_style.fill_color = text_style.stroke_color = |
| text_style.emphasis_mark_color = text_color; |
| text_style.stroke_width = style.TextStrokeWidth(); |
| text_style.shadow = 0; |
| |
| LayoutRect box_rect(box_origin, LayoutSize(inline_text_box_.LogicalWidth(), |
| inline_text_box_.LogicalHeight())); |
| LayoutPoint text_origin( |
| box_origin.X(), box_origin.Y() + 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); |
| } |
| |
| void InlineTextBoxPainter::PaintTextMatchMarkerBackground( |
| const PaintInfo& paint_info, |
| const LayoutPoint& box_origin, |
| const TextMatchMarker& marker, |
| const ComputedStyle& style, |
| const Font& font) { |
| if (!LineLayoutAPIShim::LayoutObjectFrom(inline_text_box_.GetLineLayoutItem()) |
| ->GetFrame() |
| ->GetEditor() |
| .MarkedTextMatchesAreHighlighted()) |
| return; |
| |
| const auto paint_offsets = GetMarkerPaintOffsets(marker, inline_text_box_); |
| TextRun run = inline_text_box_.ConstructTextRun(style); |
| |
| Color color = LayoutTheme::GetTheme().PlatformTextSearchHighlightColor( |
| marker.IsActiveMatch()); |
| GraphicsContext& context = paint_info.context; |
| GraphicsContextStateSaver state_saver(context); |
| |
| LayoutRect box_rect(box_origin, LayoutSize(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 |