blob: 40063a379c111d7140584ec9bf12d6085b1d78ce [file] [log] [blame]
// Copyright 2018 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/document_marker_painter.h"
#include "build/build_config.h"
#include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
#include "third_party/blink/renderer/core/layout/layout_theme.h"
#include "third_party/blink/renderer/core/paint/text_paint_style.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
namespace blink {
namespace {
#if !defined(OS_MACOSX)
static const float kMarkerWidth = 4;
static const float kMarkerHeight = 2;
sk_sp<PaintRecord> RecordMarker(Color blink_color) {
const SkColor color = blink_color.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 // defined(OS_MACOSX)
static const float kMarkerWidth = 4;
static const float kMarkerHeight = 3;
sk_sp<PaintRecord> RecordMarker(Color blink_color) {
const SkColor color = blink_color.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()->drawOval(SkRect::MakeWH(2 * kR, 2 * kR),
flags);
return recorder.finishRecordingAsPicture();
}
#endif // defined(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(
LayoutTheme::GetTheme().PlatformSpellingMarkerUnderlineColor())
.release()));
DEFINE_STATIC_LOCAL(
PaintRecord*, grammar_marker,
(RecordMarker(
LayoutTheme::GetTheme().PlatformGrammarMarkerUnderlineColor())
.release()));
auto* const 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 defined(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 DocumentMarkerPainter::PaintStyleableMarkerUnderline(
GraphicsContext& context,
const LayoutPoint& box_origin,
const StyleableMarker& marker,
const ComputedStyle& style,
const FloatRect& marker_rect,
LayoutUnit logical_height) {
if (marker.HasThicknessNone() ||
(marker.UnderlineColor() == Color::kTransparent &&
!marker.UseTextColor()))
return;
// start of line to draw, relative to box_origin.X()
LayoutUnit start = LayoutUnit(marker_rect.X());
LayoutUnit width = LayoutUnit(marker_rect.Width());
// 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;
// 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 = style.GetFont().PrimaryFont();
DCHECK(font_data);
int baseline = font_data ? font_data->GetFontMetrics().Ascent() : 0;
if (marker.HasThicknessThick() && logical_height.ToInt() - baseline >= 2)
line_thickness = 2;
// Line thickness should change with zoom.
line_thickness *= style.EffectiveZoom();
Color marker_color =
marker.UseTextColor()
? style.VisitedDependentColor(GetCSSPropertyWebkitTextFillColor())
: marker.UnderlineColor();
context.SetStrokeColor(marker_color);
context.SetStrokeThickness(line_thickness);
context.DrawLineForText(
FloatPoint(
box_origin.X() + start,
(box_origin.Y() + logical_height.ToInt() - line_thickness).ToFloat()),
width);
}
static const int kMisspellingLineThickness = 3;
void DocumentMarkerPainter::PaintDocumentMarker(
GraphicsContext& context,
const LayoutPoint& box_origin,
const ComputedStyle& style,
DocumentMarker::MarkerType marker_type,
const LayoutRect& local_rect) {
// 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 = style.GetFont().PrimaryFont();
DCHECK(font_data);
int baseline = font_data->GetFontMetrics().Ascent();
int descent = (local_rect.Height() - 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 = (local_rect.Height() - 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() + local_rect.X()).ToFloat(),
(box_origin.Y() + underline_offset).ToFloat()),
local_rect.Width().ToFloat(), marker_type,
style.EffectiveZoom());
}
TextPaintStyle DocumentMarkerPainter::ComputeTextPaintStyleFrom(
const ComputedStyle& style,
const TextMatchMarker& marker) {
const Color text_color =
LayoutTheme::GetTheme().PlatformTextSearchColor(marker.IsActiveMatch());
if (style.VisitedDependentColor(GetCSSPropertyColor()) == text_color)
return {};
TextPaintStyle 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 = nullptr;
return text_style;
}
} // namespace blink