1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145

pdf / pdf_ink_brush.cc [blame]

// 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 "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_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"

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));
}

ink::Brush CreateInkBrush(PdfInkBrush::Type type, SkColor color, float size) {
  CHECK(PdfInkBrush::IsToolSizeInRange(size));

  // TODO(crbug.com/353942923): Use real values here.
  ink::BrushTip tip;
  tip.corner_rounding = GetCornerRounding(type);
  tip.opacity_multiplier = GetOpacity(type);

  // TODO(crbug.com/353942923): Use real `uri_string` here.
  auto family = ink::BrushFamily::Create(tip, ink::BrushPaint(),
                                         /*uri_string=*/"");
  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(std::move(size));
  CHECK(size_result.ok());
}

}  // namespace chrome_pdf