blob: 9dd84e3035c3c8d422df9c0267c3184ff242a681 [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.
#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/expected.h"
#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 add and remove 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,
kAdd,
kRemove,
};
using IdSet = std::set<IdType, IdTypeComparator>;
using AddCommands = base::StrongAlias<class AddCommandsTag, IdSet>;
using RemoveCommands = base::StrongAlias<class RemoveCommandsTag, IdSet>;
using Commands = std::variant<std::monostate, AddCommands, RemoveCommands>;
PdfInkUndoRedoModel();
PdfInkUndoRedoModel(const PdfInkUndoRedoModel&) = delete;
PdfInkUndoRedoModel& operator=(const PdfInkUndoRedoModel&) = delete;
~PdfInkUndoRedoModel();
// For all Add / Remove methods:
// - The expected usage is: 1 StartOp call, any number of Op(Variant) calls,
// 1 FinishOp call.
// - StartOp returns the lowest annotation ID among added elements to discard,
// or nullopt if there are no elements to discard on success. Returns
// `std::monostate` 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 `std::monostate` or false in production code. Returning
// `std::monostate` or false is only allowed in tests to check failure modes
// without resorting to death tests.
// Starts recording add 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. Returns the lowest annotation ID
// among added elements to discard. Since IDs are added in increasing order,
// all elements with the same ID or larger IDs can be discarded. This does not
// return `InkModeledShapeId`, because model shapes are pre-existing and
// cannot be discarded.
// Must be called before Add().
// Must not be called while another add/remove has been started.
[[nodiscard]] base::expected<std::optional<IdType>, std::monostate>
StartAdd();
// Records adding an annotation identified by `id`.
// Must be called between StartAdd() and FinishAdd().
// Callers must ensure that IDs added are in increasing order.
// `id` must not be on the commands stack.
// `id` must not be an `InkModeledShapeId`.
[[nodiscard]] bool Add(IdType id);
// Finishes recording add commands and pushes a new element onto the stack.
// Must be called after StartAdd().
[[nodiscard]] bool FinishAdd();
// Starts recording remove 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. Returns the lowest annotation ID
// among added elements to discard. Since IDs are added in increasing order,
// all elements with the same ID or larger IDs can be discarded. This does not
// return `InkModeledShapeId`, because model shapes are pre-existing and
// cannot be discarded.
// Must be called before Remove().
// Must not be called while another add/remove has been started.
[[nodiscard]] base::expected<std::optional<IdType>, std::monostate>
StartRemove();
// Records erasing an annotation identified by `id`.
// Must be called between StartRemove() and FinishRemove().
// `id` must not be in any `RemoveCommands` on the commands stack.
// If `id` is for a stroke, it must be in a `AddCommands` on the commands
// stack.
// If the caller passes in invalid values, `PdfInkUndoRedoModel` will
// faithfully give them back during undo/redo operations.
[[nodiscard]] bool Remove(IdType id);
// Finishes recording remove commands and pushes a new element onto the stack.
// Must be called after StartRemove().
[[nodiscard]] bool FinishRemove();
// 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 AddCommands& GetAddCommands(const Commands& commands);
static const RemoveCommands& GetRemoveCommands(const Commands& commands);
private:
template <typename T>
base::expected<std::optional<IdType>, std::monostate> StartImpl();
bool IsAtTopOfStackWithGivenCommandType(CommandsType type) const;
bool HasIdInAddCommands(IdType id) const;
bool HasIdInRemoveCommands(IdType id) const;
// Invariants:
// (1) Never empty.
// (2) The last element and only the last element can be `std::monostate`.
// (3) IDs used in `AddCommands` elements are unique among all `AddCommands`
// elements.
// (4) IDs added to a `AddCommands` must not exist in any `RemoveCommands`.
// (5) IDs used in `RemoveCommands` elements are unique among all
// `RemoveCommands` elements.
// (6) IDs added to a `RemoveCommands` must exist in some `AddCommands`
// element.
// (7) `AddCommands` only contains `InkStrokeId` elements here. The reason
// `AddCommands` can hold `InkModeledShapeId` is to undo an
// `InkModeledShapeId` removal, 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_