blob: 69fb3617d677702a6436f5d16d5cff4d7e005ca5 [file]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "pdf/pdf_ink_brush.h"
#include <optional>
#include <vector>
#include "base/check_op.h"
#include "base/notreached.h"
#include "third_party/ink/src/ink/brush/brush.h"
#include "third_party/ink/src/ink/brush/brush_behavior.h"
#include "third_party/ink/src/ink/brush/brush_family.h"
#include "third_party/ink/src/ink/brush/brush_paint.h"
#include "third_party/ink/src/ink/brush/brush_tip.h"
#include "third_party/ink/src/ink/color/color.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
namespace chrome_pdf {
namespace {
float GetCornerRounding(PdfInkBrush::Type type) {
switch (type) {
case PdfInkBrush::Type::kHighlighter:
return 0.0f;
case PdfInkBrush::Type::kPen:
return 1.0f;
}
NOTREACHED();
}
float GetOpacity(PdfInkBrush::Type type) {
switch (type) {
case PdfInkBrush::Type::kHighlighter:
// LINT.IfChange(HighlighterOpacity)
return 0.4f;
// LINT.ThenChange(//chrome/browser/resources/pdf/pdf_viewer_utils.ts:HighlighterOpacity)
case PdfInkBrush::Type::kPen:
return 1.0f;
}
NOTREACHED();
}
// ink::Brush actually uses ink::Color, but pdf/ uses SkColor. To avoid having
// multiple color representations, do not expose ink::Color and just convert
// `color`.
ink::Color GetInkColorFromSkColor(SkColor color) {
return ink::Color::FromUint8(
/*red=*/SkColorGetR(color),
/*green=*/SkColorGetG(color),
/*blue=*/SkColorGetB(color),
/*alpha=*/SkColorGetA(color));
}
std::vector<ink::BrushBehavior> GetTipBehaviors(PdfInkBrush::Type type) {
switch (type) {
case PdfInkBrush::Type::kHighlighter:
return {};
case PdfInkBrush::Type::kPen:
return {
ink::BrushBehavior{{
ink::BrushBehavior::SourceNode{
.source = ink::BrushBehavior::Source::kNormalizedPressure,
.source_value_range = {0.8, 1},
},
ink::BrushBehavior::ToolTypeFilterNode{{.stylus = true}},
ink::BrushBehavior::DampingNode{
.damping_source =
ink::BrushBehavior::DampingSource::kTimeInSeconds,
.damping_gap = 0.025,
},
ink::BrushBehavior::TargetNode{
.target = ink::BrushBehavior::Target::kSizeMultiplier,
.target_modifier_range = {1, 1.5},
},
}},
ink::BrushBehavior{{
ink::BrushBehavior::SourceNode{
.source =
ink::BrushBehavior::Source::kPredictedTimeElapsedInMillis,
.source_value_range = {0, 24},
},
ink::BrushBehavior::SourceNode{
.source = ink::BrushBehavior::Source::
kPredictedDistanceTraveledInMultiplesOfBrushSize,
.source_value_range = {1.5, 2},
},
ink::BrushBehavior::ResponseNode{
.response_curve =
{ink::EasingFunction::Predefined::kEaseInOut},
},
ink::BrushBehavior::BinaryOpNode{
.operation = ink::BrushBehavior::BinaryOp::kProduct,
},
ink::BrushBehavior::TargetNode{
.target = ink::BrushBehavior::Target::kOpacityMultiplier,
.target_modifier_range = {1, 0.3},
},
}}};
}
NOTREACHED();
}
ink::Brush CreateInkBrush(PdfInkBrush::Type type, SkColor color, float size) {
ink::BrushTip tip;
tip.corner_rounding = GetCornerRounding(type);
tip.behaviors = GetTipBehaviors(type);
ink::BrushPaint paint;
paint.color_functions.emplace_back(
ink::ColorFunction::OpacityMultiplier{.multiplier = GetOpacity(type)});
// TODO(crbug.com/353942923): Use real `client_brush_family_id` here.
auto family = ink::BrushFamily::Create(tip, paint,
/*client_brush_family_id=*/"");
CHECK(family.ok());
auto brush = ink::Brush::Create(*family,
/*color=*/
GetInkColorFromSkColor(color),
/*size=*/size,
/*epsilon=*/0.1f);
CHECK(brush.ok());
return *brush;
}
// Determine the area to invalidate centered around a point where a brush is
// applied.
gfx::Rect GetPointInvalidateArea(float brush_diameter,
const gfx::PointF& center) {
// Choose a rectangle that surrounds the point for the brush radius.
float brush_radius = brush_diameter / 2;
return gfx::ToEnclosingRect(gfx::RectF(center.x() - brush_radius,
center.y() - brush_radius,
brush_diameter, brush_diameter));
}
} // namespace
// static
std::optional<PdfInkBrush::Type> PdfInkBrush::StringToType(
const std::string& brush_type) {
if (brush_type == "highlighter") {
return Type::kHighlighter;
}
if (brush_type == "pen") {
return Type::kPen;
}
return std::nullopt;
}
// static
std::string PdfInkBrush::TypeToString(Type brush_type) {
switch (brush_type) {
case Type::kHighlighter:
return "highlighter";
case Type::kPen:
return "pen";
}
NOTREACHED();
}
// static
bool PdfInkBrush::IsToolSizeInRange(float size) {
return size >= 1 && size <= 16;
}
PdfInkBrush::PdfInkBrush(Type brush_type, SkColor color, float size)
: ink_brush_(CreateInkBrush(brush_type, color, size)) {}
PdfInkBrush::~PdfInkBrush() = default;
gfx::Rect PdfInkBrush::GetInvalidateArea(const gfx::PointF& center1,
const gfx::PointF& center2) const {
// For a line connecting `center1` to `center2`, the invalidate
// region is the union between the areas affected by them both.
float brush_diameter = ink_brush_.GetSize();
gfx::Rect area1 = GetPointInvalidateArea(brush_diameter, center1);
gfx::Rect area2 = GetPointInvalidateArea(brush_diameter, center2);
area2.Union(area1);
return area2;
}
void PdfInkBrush::SetColor(SkColor color) {
ink_brush_.SetColor(GetInkColorFromSkColor(color));
}
void PdfInkBrush::SetSize(float size) {
auto size_result = ink_brush_.SetSize(size);
CHECK(size_result.ok());
}
} // namespace chrome_pdf