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 146
|
// 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_UNDO_REDO_MODEL_H_
#define PDF_PDF_INK_UNDO_REDO_MODEL_H_
#include <stddef.h>
#include <optional>
#include <set>
#include <variant>
#include <vector>
#include "base/types/strong_alias.h"
#include "pdf/buildflags.h"
#include "pdf/pdf_ink_ids.h"
static_assert(BUILDFLAG(ENABLE_PDF_INK2), "ENABLE_PDF_INK2 not set to true");
namespace chrome_pdf {
// Models draw and erase commands. Based on the recorded commands,
// processes undo / redo requests and calculates what commands need to be
// applied.
class PdfInkUndoRedoModel {
public:
enum class CommandsType {
kNone,
kDraw,
kErase,
};
// Set of IDs to draw/erase. There are multiple types of IDs:
// - `InkStrokeId` is for strokes that are first drawn, and maybe erased
// later.
// - `InkModeledShapeId` is for modeled shapes that are pre-existing and can
// be erased.
using IdType = std::variant<InkStrokeId, InkModeledShapeId>;
using DrawCommands =
base::StrongAlias<class DrawCommandsTag, std::set<IdType>>;
using EraseCommands =
base::StrongAlias<class EraseCommandsTag, std::set<IdType>>;
using Commands = std::variant<std::monostate, DrawCommands, EraseCommands>;
// Set of IDs used for drawing to discard. This does not use `IdType`, because
// model shapes are pre-existing and cannot be discarded.
using DiscardedDrawCommands = std::set<InkStrokeId>;
PdfInkUndoRedoModel();
PdfInkUndoRedoModel(const PdfInkUndoRedoModel&) = delete;
PdfInkUndoRedoModel& operator=(const PdfInkUndoRedoModel&) = delete;
~PdfInkUndoRedoModel();
// For all Draw / Erase methods:
// - The expected usage is: 1 StartOp call, any number of Op(Variant) calls,
// 1 FinishOp call.
// - StartOp returns a non-null, but possible empty value on success. Returns
// nullopt if any requirements are not met.
// - Op(Variant) and FinishOp return true on success. Return false if any
// requirements are not met.
// - Must not return false in production code. Returning false is only allowed
// in tests to check failure modes without resorting to death tests.
// Starts recording draw commands. If the current commands stack position is
// not at the top of the stack, then this discards all entries from the
// current position to the top of the stack. The caller can discard its
// entries with IDs that match the returned values.
// Must be called before Draw().
// Must not be called while another draw/erase has been started.
[[nodiscard]] std::optional<DiscardedDrawCommands> StartDraw();
// Records drawing a stroke identified by `id`.
// Must be called between StartDraw() and FinishDraw().
// `id` must not be on the commands stack.
[[nodiscard]] bool Draw(InkStrokeId id);
// Finishes recording draw commands and pushes a new element onto the stack.
// Must be called after StartDraw().
[[nodiscard]] bool FinishDraw();
// Starts recording erase commands. If the current commands stack position is
// not at the top of the stack, then this discards all entries from the
// current position to the top of the stack. The caller can discard its
// entries with IDs that match the returned values.
// Must be called before EraseStroke() and EraseShape().
// Must not be called while another draw/erase has been started.
[[nodiscard]] std::optional<DiscardedDrawCommands> StartErase();
// Records erasing a stroke identified by `id`.
// Must be called between StartErase() and FinishErase().
// `id` must be in a `DrawCommands` on the commands stack.
// `id` must not be in any `EraseCommands` on the commands stack.
[[nodiscard]] bool EraseStroke(InkStrokeId id);
// Records erasing a shape identified by `id`.
// Must be called between StartErase() and FinishErase().
// `id` must not be in any `EraseCommands` on the commands stack.
// Unlike EraseStroke(), EraseShape() has no corresponding draw method, so it
// relies on the caller to pass in valid `id` values. If the caller passes in
// invalid values, `PdfInkUndoRedoModel` will faithfully give them back during
// undo/redo operations.
[[nodiscard]] bool EraseShape(InkModeledShapeId id);
// Finishes recording erase commands and pushes a new element onto the stack.
// Must be called after StartErase().
[[nodiscard]] bool FinishErase();
// Returns the commands that needs to be applied to satisfy the undo / redo
// request and moves the position in the commands stack without modifying the
// commands themselves.
Commands Undo();
Commands Redo();
static CommandsType GetCommandsType(const Commands& commands);
static const DrawCommands& GetDrawCommands(const Commands& commands);
static const EraseCommands& GetEraseCommands(const Commands& commands);
private:
template <typename T>
std::optional<DiscardedDrawCommands> StartImpl();
bool IsAtTopOfStackWithGivenCommandType(CommandsType type) const;
bool HasIdInDrawCommands(IdType id) const;
bool HasIdInEraseCommands(IdType id) const;
// Invariants:
// (1) Never empty.
// (2) The last element and only the last element can be `std::monostate`.
// (3) IDs used in `DrawCommands` elements are unique among all `DrawCommands`
// elements.
// (4) IDs added to a `DrawCommands` must not exist in any `EraseCommands`.
// (5) IDs used in `EraseCommands` elements are unique among all
// `EraseCommands` elements.
// (6) IDs added to a `EraseCommands` must exist in some `DrawCommands`
// element.
// (7) `DrawCommands` only contains `InkStrokeId` elements here. The reason
// `DrawCommands` can hold `InkModeledShapeId` is to undo an
// `InkModeledShapeId` erasure, where the caller needs to know they need
// to draw the shape.
std::vector<Commands> commands_stack_ = {std::monostate()};
// Invariants:
// (8) Always less than the size of `commands_stack_`.
size_t stack_position_ = 0;
};
} // namespace chrome_pdf
#endif // PDF_PDF_INK_UNDO_REDO_MODEL_H_
|