0

[PDF Ink Signatures] Use custom cursor for drawing tools

Use GenerateToolCursor() in PdfInkModule to draw custom cursors that
match the color/size of the selected drawing tool.

Bug: 342602620
Change-Id: Ib05f8ca9ebf8575b9d57fde37b80e2df9c87af93
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5778151
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Andy Phan <andyphan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1341401}
This commit is contained in:
Lei Zhang
2024-08-14 01:25:20 +00:00
committed by Chromium LUCI CQ
parent 5533aa28bf
commit 436e7bc0bf
6 changed files with 155 additions and 11 deletions

@ -36,9 +36,11 @@
#include "pdf/input_utils.h"
#include "pdf/pdf_features.h"
#include "pdf/pdf_ink_brush.h"
#include "pdf/pdf_ink_cursor.h"
#include "pdf/pdf_ink_transform.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
@ -557,6 +559,7 @@ void PdfInkModule::HandleSetAnnotationBrushMessage(
if (brush_type_string == "eraser") {
auto& eraser_state = current_tool_state_.emplace<EraserState>();
eraser_state.eraser_size = size;
MaybeSetCursor();
return;
}
@ -583,11 +586,13 @@ void PdfInkModule::HandleSetAnnotationBrushMessage(
current_tool_state_.emplace<DrawingStrokeState>();
drawing_stroke_state().brush =
std::make_unique<PdfInkBrush>(brush_type.value(), params);
MaybeSetCursor();
}
void PdfInkModule::HandleSetAnnotationModeMessage(
const base::Value::Dict& message) {
enabled_ = message.FindBool("enable").value();
MaybeSetCursor();
}
std::vector<std::unique_ptr<InkInProgressStroke>>
@ -763,6 +768,30 @@ void PdfInkModule::ApplyUndoRedoDiscards(
}
}
void PdfInkModule::MaybeSetCursor() {
if (!enabled()) {
// Do nothing when disabled. The code outside of PdfInkModule will select a
// normal mouse cursor and switch to that.
return;
}
SkColor color;
float brush_size;
if (is_drawing_stroke()) {
const auto& ink_brush = drawing_stroke_state().brush->GetInkBrush();
color = ink_brush.GetColor();
brush_size = ink_brush.GetSize();
} else {
CHECK(is_erasing_stroke());
constexpr SkColor kEraserColor = SK_ColorWHITE;
color = kEraserColor;
brush_size = erasing_stroke_state().eraser_size;
}
client_->UpdateInkCursorImage(
GenerateToolCursor(color, CursorDiameterFromBrushSize(brush_size)));
}
PdfInkModule::DrawingStrokeState::DrawingStrokeState() = default;
PdfInkModule::DrawingStrokeState::~DrawingStrokeState() = default;

@ -23,6 +23,7 @@
#include "pdf/page_orientation.h"
#include "pdf/pdf_ink_undo_redo_model.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
@ -78,9 +79,6 @@ class PdfInkModule {
// Gets current zoom factor.
virtual float GetZoom() const = 0;
// Notifies the client that a stroke has finished drawing or erasing.
virtual void StrokeFinished() {}
// Notifies the client to invalidate the `rect`. Coordinates are
// screen-based, based on the same viewport origin that was used to specify
// the `blink::WebMouseEvent` positions during stroking.
@ -89,6 +87,12 @@ class PdfInkModule {
// Returns whether the page at `index` is visible or not.
virtual bool IsPageVisible(int index) = 0;
// Notifies the client that a stroke has finished drawing or erasing.
virtual void StrokeFinished() {}
// Asks the client to change the cursor to `bitmap`.
virtual void UpdateInkCursorImage(SkBitmap bitmap) {}
// Returns the 0-based page index for the given `point` if it is on a
// visible page, or -1 if `point` is not on a visible page.
virtual int VisiblePageIndexFromPoint(const gfx::PointF& point) = 0;
@ -276,6 +280,8 @@ class PdfInkModule {
void ApplyUndoRedoDiscards(
const PdfInkUndoRedoModel::DiscardedDrawCommands& discards);
void MaybeSetCursor();
const raw_ref<Client> client_;
bool enabled_ = false;

@ -33,8 +33,10 @@
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d_f.h"
using testing::_;
using testing::ElementsAre;
using testing::ElementsAreArray;
using testing::InSequence;
using testing::Pair;
namespace chrome_pdf {
@ -109,6 +111,8 @@ class FakeClient : public PdfInkModule::Client {
void StrokeFinished() override { ++stroke_finished_count_; }
MOCK_METHOD(void, UpdateInkCursorImage, (SkBitmap bitmap), (override));
int VisiblePageIndexFromPoint(const gfx::PointF& point) override {
for (size_t i = 0; i < page_layouts_.size(); ++i) {
if (IsPageVisible(i) && page_layouts_[i].Contains(point)) {
@ -188,7 +192,7 @@ TEST_F(PdfInkModuleTest, UnknownMessage) {
// Verify that a set eraser message sets the annotation brush to an eraser.
TEST_F(PdfInkModuleTest, HandleSetAnnotationBrushMessageEraser) {
EnableAnnotationMode();
EXPECT_EQ(true, ink_module().enabled());
EXPECT_TRUE(ink_module().enabled());
base::Value::Dict message = CreateSetAnnotationBrushMessageForTesting(
"eraser", /*size=*/2.5, nullptr);
@ -204,7 +208,7 @@ TEST_F(PdfInkModuleTest, HandleSetAnnotationBrushMessageEraser) {
// given params.
TEST_F(PdfInkModuleTest, HandleSetAnnotationBrushMessagePen) {
EnableAnnotationMode();
EXPECT_EQ(true, ink_module().enabled());
EXPECT_TRUE(ink_module().enabled());
TestAnnotationBrushMessageParams message_params{/*color_r=*/10,
/*color_g=*/255,
@ -226,7 +230,7 @@ TEST_F(PdfInkModuleTest, HandleSetAnnotationBrushMessagePen) {
// highlighter, with the given params.
TEST_F(PdfInkModuleTest, HandleSetAnnotationBrushMessageHighlighter) {
EnableAnnotationMode();
EXPECT_EQ(true, ink_module().enabled());
EXPECT_TRUE(ink_module().enabled());
TestAnnotationBrushMessageParams message_params{/*color_r=*/240,
/*color_g=*/133,
@ -248,7 +252,7 @@ TEST_F(PdfInkModuleTest, HandleSetAnnotationBrushMessageHighlighter) {
// brush.
TEST_F(PdfInkModuleTest, HandleSetAnnotationBrushMessageColorZero) {
EnableAnnotationMode();
EXPECT_EQ(true, ink_module().enabled());
EXPECT_TRUE(ink_module().enabled());
TestAnnotationBrushMessageParams message_params{/*color_r=*/0, /*color_g=*/0,
/*color_b=*/0};
@ -283,6 +287,58 @@ TEST_F(PdfInkModuleTest, HandleSetAnnotationModeMessage) {
EXPECT_FALSE(ink_module().enabled());
}
TEST_F(PdfInkModuleTest, MaybeSetCursorWhenTogglingAnnotationMode) {
EXPECT_FALSE(ink_module().enabled());
EXPECT_CALL(client(), UpdateInkCursorImage(_))
.WillOnce(
[this](SkBitmap bitmap) { EXPECT_TRUE(ink_module().enabled()); });
base::Value::Dict message =
CreateSetAnnotationModeMessageForTesting(/*enable=*/true);
EXPECT_TRUE(ink_module().OnMessage(message));
EXPECT_TRUE(ink_module().enabled());
message.Set("enable", false);
EXPECT_TRUE(ink_module().OnMessage(message));
EXPECT_FALSE(ink_module().enabled());
}
TEST_F(PdfInkModuleTest, MaybeSetCursorWhenChangingBrushes) {
{
InSequence seq;
EXPECT_CALL(client(), UpdateInkCursorImage(_))
.WillOnce([](SkBitmap bitmap) {
EXPECT_EQ(6, bitmap.width());
EXPECT_EQ(6, bitmap.height());
});
EXPECT_CALL(client(), UpdateInkCursorImage(_))
.WillOnce([](SkBitmap bitmap) {
EXPECT_EQ(20, bitmap.width());
EXPECT_EQ(20, bitmap.height());
});
EXPECT_CALL(client(), UpdateInkCursorImage(_))
.WillOnce([](SkBitmap bitmap) {
EXPECT_EQ(10, bitmap.width());
EXPECT_EQ(10, bitmap.height());
});
}
EnableAnnotationMode();
EXPECT_TRUE(ink_module().enabled());
TestAnnotationBrushMessageParams message_params{/*color_r=*/0,
/*color_g=*/255,
/*color_b=*/0};
base::Value::Dict message = CreateSetAnnotationBrushMessageForTesting(
"pen", /*size=*/16.0, &message_params);
EXPECT_TRUE(ink_module().OnMessage(message));
message = CreateSetAnnotationBrushMessageForTesting("eraser", /*size=*/8.0,
nullptr);
EXPECT_TRUE(ink_module().OnMessage(message));
}
class PdfInkModuleStrokeTest : public PdfInkModuleTest {
protected:
// Mouse locations used for `RunStrokeCheckTest()`.

@ -916,6 +916,14 @@ void PdfViewWebPlugin::NavigateToDestination(int page,
}
void PdfViewWebPlugin::UpdateCursor(ui::mojom::CursorType new_cursor_type) {
#if BUILDFLAG(ENABLE_PDF_INK2)
if (ink_module_ && ink_module_->enabled()) {
// Block normal mouse cursor updates, so the cursor set by PdfInkModule
// while it is enabled does not get overwritten.
return;
}
#endif
cursor_ = new_cursor_type;
}
@ -2036,6 +2044,11 @@ void PdfViewWebPlugin::StrokeFinished() {
client_->PostMessage(std::move(message));
}
void PdfViewWebPlugin::UpdateInkCursorImage(SkBitmap bitmap) {
gfx::Point hotspot(bitmap.width() / 2, bitmap.height() / 2);
cursor_ = ui::Cursor::NewCustom(std::move(bitmap), std::move(hotspot));
}
int PdfViewWebPlugin::VisiblePageIndexFromPoint(const gfx::PointF& point) {
for (int i = 0; i < engine_->GetNumberOfPages(); ++i) {
if (!IsPageVisible(i)) {

@ -403,13 +403,14 @@ class PdfViewWebPlugin final : public PDFiumEngineClient,
int32_t page_object_index) override;
#if BUILDFLAG(ENABLE_PDF_INK2)
// PdfInkModule:
// PdfInkModule::Client:
PageOrientation GetOrientation() const override;
gfx::Rect GetPageContentsRect(int index) override;
gfx::Vector2dF GetViewportOriginOffset() override;
float GetZoom() const override;
bool IsPageVisible(int index) override;
void StrokeFinished() override;
void UpdateInkCursorImage(SkBitmap bitmap) override;
int VisiblePageIndexFromPoint(const gfx::PointF& point) override;
#endif

@ -19,7 +19,6 @@
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
@ -40,6 +39,7 @@
#include "pdf/pdf_accessibility_image_fetcher.h"
#include "pdf/pdf_features.h"
#include "pdf/test/mock_web_associated_url_loader.h"
#include "pdf/test/pdf_ink_test_helpers.h"
#include "pdf/test/test_helpers.h"
#include "pdf/test/test_pdfium_engine.h"
#include "printing/metafile_skia.h"
@ -94,7 +94,6 @@ using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::IsEmpty;
using ::testing::IsFalse;
using ::testing::IsTrue;
@ -2442,7 +2441,47 @@ TEST_F(PdfViewWebPluginPrintPreviewTest,
}
#if BUILDFLAG(ENABLE_PDF_INK2)
using PdfViewWebPluginInkTest = PdfViewWebPluginTest;
class PdfViewWebPluginInkTest : public PdfViewWebPluginTest {
private:
base::test::ScopedFeatureList feature_list_{features::kPdfInk2};
};
TEST_F(PdfViewWebPluginInkTest, UpdateCursor) {
UpdatePluginGeometryWithoutWaiting(2.0f, {0, 0, 20, 20});
ON_CALL(*engine_ptr_, HandleInputEvent)
.WillByDefault([this](const blink::WebInputEvent& event) -> bool {
plugin_->UpdateCursor(ui::mojom::CursorType::kPointer);
return false;
});
blink::WebMouseEvent mouse_event;
mouse_event.SetType(blink::WebInputEvent::Type::kMouseMove);
mouse_event.SetPositionInWidget(10.0f, 20.0f);
ui::Cursor cursor;
EXPECT_EQ(ui::mojom::CursorType::kNull, cursor.type());
EXPECT_EQ(blink::WebInputEventResult::kNotHandled,
plugin_->HandleInputEvent(
blink::WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()),
&cursor));
EXPECT_EQ(ui::mojom::CursorType::kPointer, cursor.type());
plugin_->OnMessage(CreateSetAnnotationModeMessageForTesting(/*enable=*/true));
EXPECT_EQ(blink::WebInputEventResult::kNotHandled,
plugin_->HandleInputEvent(
blink::WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()),
&cursor));
EXPECT_EQ(ui::mojom::CursorType::kCustom, cursor.type());
plugin_->OnMessage(
CreateSetAnnotationModeMessageForTesting(/*enable=*/false));
EXPECT_EQ(blink::WebInputEventResult::kNotHandled,
plugin_->HandleInputEvent(
blink::WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()),
&cursor));
EXPECT_EQ(ui::mojom::CursorType::kPointer, cursor.type());
}
TEST_F(PdfViewWebPluginInkTest, VisiblePageIndexFromPoint) {
ON_CALL(*engine_ptr_, GetPageContentsRect)