
Make the behavior in this scenario more consistent with other apps. Change PdfInkModule's to remember if it ever received a pen event, and ignore touch events after that. Update tests with the new expectations, add ApplyStrokeWithTouchAtPointsNotHandled() as needed, and remove the now fulfilled TODOs. Bug: 392650039 Change-Id: I38c45d8fc369748c49619e3448f713651e5e3797 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6216548 Commit-Queue: Lei Zhang <thestig@chromium.org> Reviewed-by: Alan Screen <awscreen@chromium.org> Cr-Commit-Position: refs/heads/main@{#1413686}
433 lines
16 KiB
C++
433 lines
16 KiB
C++
// 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.
|
|
|
|
#ifndef PDF_PDF_INK_MODULE_H_
|
|
#define PDF_PDF_INK_MODULE_H_
|
|
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <vector>
|
|
|
|
#include "base/containers/flat_set.h"
|
|
#include "base/gtest_prod_util.h"
|
|
#include "base/memory/raw_ref.h"
|
|
#include "base/time/time.h"
|
|
#include "base/values.h"
|
|
#include "pdf/buildflags.h"
|
|
#include "pdf/pdf_ink_brush.h"
|
|
#include "pdf/pdf_ink_ids.h"
|
|
#include "pdf/pdf_ink_undo_redo_model.h"
|
|
#include "third_party/abseil-cpp/absl/types/variant.h"
|
|
#include "third_party/ink/src/ink/geometry/partitioned_mesh.h"
|
|
#include "third_party/ink/src/ink/strokes/in_progress_stroke.h"
|
|
#include "third_party/ink/src/ink/strokes/input/stroke_input.h"
|
|
#include "third_party/ink/src/ink/strokes/input/stroke_input_batch.h"
|
|
#include "third_party/ink/src/ink/strokes/stroke.h"
|
|
#include "ui/gfx/geometry/point_f.h"
|
|
|
|
static_assert(BUILDFLAG(ENABLE_PDF_INK2), "ENABLE_PDF_INK2 not set to true");
|
|
|
|
class SkCanvas;
|
|
|
|
namespace blink {
|
|
class WebInputEvent;
|
|
class WebMouseEvent;
|
|
class WebTouchEvent;
|
|
} // namespace blink
|
|
|
|
namespace chrome_pdf {
|
|
|
|
class PdfInkModuleClient;
|
|
|
|
class PdfInkModule {
|
|
private:
|
|
// Some initial definitions needed for internal working of public classes.
|
|
|
|
// A stroke that has been completed, its ID, and whether it should be drawn
|
|
// or not.
|
|
struct FinishedStrokeState {
|
|
FinishedStrokeState(ink::Stroke stroke, InkStrokeId id);
|
|
FinishedStrokeState(const FinishedStrokeState&) = delete;
|
|
FinishedStrokeState& operator=(const FinishedStrokeState&) = delete;
|
|
FinishedStrokeState(FinishedStrokeState&&) noexcept;
|
|
FinishedStrokeState& operator=(FinishedStrokeState&&) noexcept;
|
|
~FinishedStrokeState();
|
|
|
|
// Coordinates for each stroke are stored in a canonical format specified in
|
|
// pdf_ink_transform.h.
|
|
ink::Stroke stroke;
|
|
|
|
// A unique ID to identify this stroke.
|
|
InkStrokeId id;
|
|
|
|
bool should_draw = true;
|
|
};
|
|
|
|
// Each page of a document can have many strokes. Each stroke is restricted
|
|
// to just one page.
|
|
// The elements are stored with IDs in an increasing order.
|
|
using PageStrokes = std::vector<FinishedStrokeState>;
|
|
|
|
// Mapping of a 0-based page index to the strokes for that page.
|
|
using DocumentStrokesMap = std::map<int, PageStrokes>;
|
|
|
|
public:
|
|
using StrokeInputPoints = std::vector<gfx::PointF>;
|
|
|
|
// Each page of a document can have many strokes. The input points for each
|
|
// stroke are restricted to just one page.
|
|
using PageStrokeInputPoints = std::vector<StrokeInputPoints>;
|
|
|
|
// Mapping of a 0-based page index to the input points that make up the
|
|
// strokes for that page.
|
|
using DocumentStrokeInputPointsMap = std::map<int, PageStrokeInputPoints>;
|
|
|
|
struct PageInkStroke {
|
|
int page_index;
|
|
raw_ref<const ink::Stroke> stroke;
|
|
};
|
|
|
|
// Iterator to get visible strokes. Once created, the caller should ensure
|
|
// that there is no further PdfInkModule interactions until the iterator has
|
|
// been destroyed.
|
|
class PageInkStrokeIterator {
|
|
public:
|
|
explicit PageInkStrokeIterator(const DocumentStrokesMap& strokes);
|
|
PageInkStrokeIterator(const PageInkStrokeIterator&) = delete;
|
|
PageInkStrokeIterator& operator=(const PageInkStrokeIterator&) = delete;
|
|
~PageInkStrokeIterator();
|
|
|
|
// Gets the next visible stroke if there is one, and advances the internal
|
|
// iterator to the next visible stroke.
|
|
std::optional<PageInkStroke> GetNextStrokeAndAdvance();
|
|
|
|
private:
|
|
// Helper to advance to the next page which has visible strokes. If there
|
|
// is another page with visible strokes, performs the iterators
|
|
// initialization to be able to get the visible strokes for it. Leaves
|
|
// `pages_iterator_` at end position if there are no more pages with
|
|
// visible strokes.
|
|
void AdvanceToNextPageWithVisibleStrokes();
|
|
|
|
// Helper to advance to the next visible stroke for the current page, if
|
|
// there is one. Leaves `page_strokes_iterator_` at end position if there
|
|
// are no more visible strokes.
|
|
void AdvanceForCurrentPage();
|
|
|
|
const raw_ref<const DocumentStrokesMap> strokes_;
|
|
|
|
// Iterator for getting pages with visible strokes.
|
|
DocumentStrokesMap::const_iterator pages_iterator_;
|
|
|
|
// Iterator for getting visible strokes of a particular page.
|
|
PageStrokes::const_iterator page_strokes_iterator_;
|
|
};
|
|
|
|
explicit PdfInkModule(PdfInkModuleClient& client);
|
|
PdfInkModule(const PdfInkModule&) = delete;
|
|
PdfInkModule& operator=(const PdfInkModule&) = delete;
|
|
~PdfInkModule();
|
|
|
|
bool enabled() const { return enabled_; }
|
|
|
|
// Determines if there are any `drawing_stroke_state().inputs` to be drawn.
|
|
bool HasInputsToDraw() const;
|
|
|
|
// Draws `drawing_stroke_state().inputs` into `canvas`. Must be in a drawing
|
|
// stroke state with non-empty `drawing_stroke_state().inputs`.
|
|
void Draw(SkCanvas& canvas);
|
|
|
|
// Draws `strokes_` for `page_index` into `canvas`. Here, `canvas` only covers
|
|
// the region for the page at `page_index`, so this only draws strokes for
|
|
// that page, regardless of page visibility.
|
|
bool DrawThumbnail(SkCanvas& canvas, int page_index);
|
|
|
|
// Gets an iterator for the visible strokes across all pages.
|
|
// Modifying the set of visible strokes while using the iterator is not
|
|
// supported and can result in undefined behavior.
|
|
PageInkStrokeIterator GetVisibleStrokesIterator();
|
|
|
|
// Returns whether the event was handled or not.
|
|
bool HandleInputEvent(const blink::WebInputEvent& event);
|
|
|
|
// Returns whether the message was handled or not.
|
|
bool OnMessage(const base::Value::Dict& message);
|
|
|
|
// Informs PdfInkModule that the plugin geometry changed.
|
|
void OnGeometryChanged();
|
|
|
|
// For testing only. Returns the current `PdfInkBrush` used to draw strokes,
|
|
// or nullptr if there is no brush.
|
|
const PdfInkBrush* GetPdfInkBrushForTesting() const;
|
|
|
|
// For testing only. Returns the current eraser size, or nullopt if the
|
|
// eraser is not in use.
|
|
std::optional<float> GetEraserSizeForTesting() const;
|
|
|
|
// For testing only. Returns the (visible) input positions used for all
|
|
// strokes in the document.
|
|
DocumentStrokeInputPointsMap GetStrokesInputPositionsForTesting() const;
|
|
DocumentStrokeInputPointsMap GetVisibleStrokesInputPositionsForTesting()
|
|
const;
|
|
|
|
// For testing only. Returns the number of stroke inputs of a particular
|
|
// `tool_type` for a given page at `page_index`. The `page_index` must be
|
|
// non-negative.
|
|
int GetInputOfTypeCountForPageForTesting(
|
|
int page_index,
|
|
ink::StrokeInput::ToolType tool_type) const;
|
|
|
|
private:
|
|
FRIEND_TEST_ALL_PREFIXES(PdfInkModuleTest, HandleSetAnnotationModeMessage);
|
|
|
|
// A shape that was loaded from a "V2" path from the PDF itself, its ID, and
|
|
// whether it should be drawn or not.
|
|
struct LoadedV2ShapeState {
|
|
LoadedV2ShapeState(ink::PartitionedMesh shape, InkModeledShapeId id);
|
|
LoadedV2ShapeState(const LoadedV2ShapeState&) = delete;
|
|
LoadedV2ShapeState& operator=(const LoadedV2ShapeState&) = delete;
|
|
LoadedV2ShapeState(LoadedV2ShapeState&&) noexcept;
|
|
LoadedV2ShapeState& operator=(LoadedV2ShapeState&&) noexcept;
|
|
~LoadedV2ShapeState();
|
|
|
|
// Coordinates for each shape are stored in a canonical format specified in
|
|
// pdf_ink_transform.h.
|
|
ink::PartitionedMesh shape;
|
|
|
|
// A unique ID to identify this shape.
|
|
InkModeledShapeId id;
|
|
|
|
bool should_draw = true;
|
|
};
|
|
|
|
// Like PageStrokes, but for shapes created from "V2" paths in the PDF.
|
|
using PageV2InkPathShapes = std::vector<LoadedV2ShapeState>;
|
|
|
|
// Like DocumentStrokesMap, but for PageV2InkPathShapes.
|
|
using DocumentV2InkPathShapesMap = std::map<int, PageV2InkPathShapes>;
|
|
|
|
struct DrawingStrokeState {
|
|
struct EventDetails {
|
|
// The event position. Coordinates match the screen-based position that
|
|
// are provided during stroking from `blink::WebMouseEvent` positions.
|
|
gfx::PointF position;
|
|
|
|
// The event time.
|
|
base::TimeTicks timestamp;
|
|
|
|
// The type of tool used to generate the input.
|
|
ink::StrokeInput::ToolType tool_type;
|
|
};
|
|
|
|
DrawingStrokeState();
|
|
DrawingStrokeState(const DrawingStrokeState&) = delete;
|
|
DrawingStrokeState& operator=(const DrawingStrokeState&) = delete;
|
|
~DrawingStrokeState();
|
|
|
|
// The current brush type to use for drawing strokes.
|
|
PdfInkBrush::Type brush_type;
|
|
|
|
std::optional<base::TimeTicks> start_time;
|
|
|
|
// The 0-based page index which is currently being stroked.
|
|
int page_index = -1;
|
|
|
|
// Details from the last input. Used after stroking has already started,
|
|
// for invalidation and for extrapolating where a stroke crosses the page
|
|
// boundary. Also used to compensate for missed events, when an end event
|
|
// was consumed by a different view and this is detected afterwards when
|
|
// PdfInkModule finally sees input events again.
|
|
std::optional<EventDetails> input_last_event;
|
|
|
|
// The points that make up the current stroke, divided into segments.
|
|
// A new segment will be necessary each time the input leaves the page
|
|
// during collection and then returns back into the original starting page.
|
|
// The coordinates added into each segment are stored in a canonical format
|
|
// specified in pdf_ink_transform.h.
|
|
std::vector<ink::StrokeInputBatch> inputs;
|
|
};
|
|
|
|
class StrokeIdGenerator {
|
|
public:
|
|
StrokeIdGenerator();
|
|
~StrokeIdGenerator();
|
|
|
|
// Returns an available ID and advance the next available ID internally.
|
|
InkStrokeId GetIdAndAdvance();
|
|
|
|
void ResetIdTo(InkStrokeId id);
|
|
|
|
private:
|
|
// The next available ID for use in FinishedStrokeState.
|
|
InkStrokeId next_stroke_id_ = InkStrokeId(0);
|
|
};
|
|
|
|
struct EraserState {
|
|
EraserState();
|
|
EraserState(const EraserState&) = delete;
|
|
EraserState& operator=(const EraserState&) = delete;
|
|
~EraserState();
|
|
|
|
bool erasing = false;
|
|
base::flat_set<int> page_indices_with_erasures;
|
|
|
|
// The event position for the last input, similar to what is stored in
|
|
// `DrawingStrokeState` for compensating for missed input events.
|
|
std::optional<gfx::PointF> input_last_event_position;
|
|
|
|
// The type of tool used to generate the input.
|
|
ink::StrokeInput::ToolType tool_type;
|
|
};
|
|
|
|
// Drawing brush state changes that are pending the completion of an
|
|
// in-progress stroke.
|
|
struct PendingDrawingBrushState {
|
|
SkColor color;
|
|
float size;
|
|
PdfInkBrush::Type type;
|
|
};
|
|
|
|
// Returns whether the event was handled or not.
|
|
bool OnMouseDown(const blink::WebMouseEvent& event);
|
|
bool OnMouseUp(const blink::WebMouseEvent& event);
|
|
bool OnMouseMove(const blink::WebMouseEvent& event);
|
|
bool OnTouchStart(const blink::WebTouchEvent& event);
|
|
bool OnTouchEnd(const blink::WebTouchEvent& event);
|
|
bool OnTouchMove(const blink::WebTouchEvent& event);
|
|
|
|
// Return values have the same semantics as On{Mouse,Touch}*() above.
|
|
bool StartStroke(const gfx::PointF& position,
|
|
base::TimeTicks timestamp,
|
|
ink::StrokeInput::ToolType tool_type);
|
|
bool ContinueStroke(const gfx::PointF& position,
|
|
base::TimeTicks timestamp,
|
|
ink::StrokeInput::ToolType tool_type);
|
|
bool FinishStroke(const gfx::PointF& position,
|
|
base::TimeTicks timestamp,
|
|
ink::StrokeInput::ToolType tool_type);
|
|
|
|
// Return values have the same semantics as On{Mouse,Touch}*() above.
|
|
bool StartEraseStroke(const gfx::PointF& position,
|
|
ink::StrokeInput::ToolType tool_type);
|
|
bool ContinueEraseStroke(const gfx::PointF& position,
|
|
ink::StrokeInput::ToolType tool_type);
|
|
bool FinishEraseStroke(const gfx::PointF& position,
|
|
ink::StrokeInput::ToolType tool_type);
|
|
|
|
// Shared code for the Erase methods above. Returns if something got erased or
|
|
// not.
|
|
bool EraseHelper(const gfx::PointF& position, int page_index);
|
|
|
|
// Sets `using_stylus_instead_of_touch_` to true if `tool_type` is
|
|
// `ink::StrokeInput::ToolType::kStylus`. Otherwise do nothing.
|
|
void MaybeRecordPenInput(ink::StrokeInput::ToolType tool_type);
|
|
|
|
// Returns true if `using_stylus_instead_of_touch_` is set, and `tool_type` is
|
|
// `ink::StrokeInput::ToolType::kTouch`.
|
|
bool ShouldIgnoreTouchInput(ink::StrokeInput::ToolType tool_type);
|
|
|
|
void HandleAnnotationRedoMessage(const base::Value::Dict& message);
|
|
void HandleAnnotationUndoMessage(const base::Value::Dict& message);
|
|
void HandleGetAnnotationBrushMessage(const base::Value::Dict& message);
|
|
void HandleSetAnnotationBrushMessage(const base::Value::Dict& message);
|
|
void HandleSetAnnotationModeMessage(const base::Value::Dict& message);
|
|
|
|
bool is_drawing_stroke() const {
|
|
return absl::holds_alternative<DrawingStrokeState>(current_tool_state_);
|
|
}
|
|
bool is_erasing_stroke() const {
|
|
return absl::holds_alternative<EraserState>(current_tool_state_);
|
|
}
|
|
const DrawingStrokeState& drawing_stroke_state() const {
|
|
return absl::get<DrawingStrokeState>(current_tool_state_);
|
|
}
|
|
DrawingStrokeState& drawing_stroke_state() {
|
|
return absl::get<DrawingStrokeState>(current_tool_state_);
|
|
}
|
|
const EraserState& erasing_stroke_state() const {
|
|
return absl::get<EraserState>(current_tool_state_);
|
|
}
|
|
EraserState& erasing_stroke_state() {
|
|
return absl::get<EraserState>(current_tool_state_);
|
|
}
|
|
|
|
// Returns the current brush. Must be in a drawing stroke state.
|
|
PdfInkBrush& GetDrawingBrush();
|
|
const PdfInkBrush& GetDrawingBrush() const;
|
|
|
|
// Returns the brush with type `brush_type`.
|
|
const PdfInkBrush& GetBrush(PdfInkBrush::Type brush_type) const;
|
|
|
|
// Converts `current_tool_state_` into segments of `ink::InProgressStroke`.
|
|
// Requires `current_tool_state_` to hold a `DrawingStrokeState`. If there is
|
|
// no `DrawingStrokeState`, or the state currently has no inputs, then the
|
|
// segments will be empty.
|
|
std::vector<ink::InProgressStroke> CreateInProgressStrokeSegmentsFromInputs()
|
|
const;
|
|
|
|
// Wrapper around EventPositionToCanonicalPosition(). `page_index` is the page
|
|
// that `position` is on. The page must be visible.
|
|
gfx::PointF ConvertEventPositionToCanonicalPosition(
|
|
const gfx::PointF& position,
|
|
int page_index);
|
|
|
|
// Helper to convert `position` to a canonical position and record it into
|
|
// `current_tool_state_` for the indicated `timestamp` and `tool_type`.
|
|
// Can only be called when drawing.
|
|
void RecordStrokePosition(const gfx::PointF& position,
|
|
base::TimeTicks timestamp,
|
|
ink::StrokeInput::ToolType tool_type);
|
|
|
|
void ApplyUndoRedoCommands(const PdfInkUndoRedoModel::Commands& commands);
|
|
void ApplyUndoRedoCommandsHelper(std::set<PdfInkUndoRedoModel::IdType> ids,
|
|
bool should_draw);
|
|
|
|
void ApplyUndoRedoDiscards(
|
|
const PdfInkUndoRedoModel::DiscardedDrawCommands& discards);
|
|
|
|
void MaybeSetCursor();
|
|
|
|
void MaybeSetDrawingBrushAndCursor();
|
|
|
|
const raw_ref<PdfInkModuleClient> client_;
|
|
|
|
bool enabled_ = false;
|
|
|
|
bool using_stylus_instead_of_touch_ = false;
|
|
|
|
bool loaded_data_from_pdf_ = false;
|
|
|
|
// Shapes loaded from the PDF.
|
|
DocumentV2InkPathShapesMap loaded_v2_shapes_;
|
|
|
|
// Generates IDs for use in FinishedStrokeState and PdfInkUndoRedoModel.
|
|
StrokeIdGenerator stroke_id_generator_;
|
|
|
|
// Store a PdfInkBrush for each brush type so that the brush parameters are
|
|
// saved when swapping between brushes. The PdfInkBrushes should not be
|
|
// modified in the middle of an in-progress stroke.
|
|
PdfInkBrush highlighter_brush_;
|
|
PdfInkBrush pen_brush_;
|
|
float eraser_size_ = 3.0f;
|
|
|
|
// The parameters that are to be applied to the drawing brushes when a new
|
|
// stroke is started. These can be modified at any time, including in the
|
|
// middle of an in-progress stroke.
|
|
std::optional<PendingDrawingBrushState> pending_drawing_brush_state_;
|
|
|
|
// The state of the current tool that is in use.
|
|
absl::variant<DrawingStrokeState, EraserState> current_tool_state_;
|
|
|
|
// The state of the strokes that have been completed.
|
|
DocumentStrokesMap strokes_;
|
|
|
|
PdfInkUndoRedoModel undo_redo_model_;
|
|
};
|
|
|
|
} // namespace chrome_pdf
|
|
|
|
#endif // PDF_PDF_INK_MODULE_H_
|