blob: 986e630774667980af0cbc657a7860e5fb00ea7b [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 "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:
using IdSet = std::set<IdType, IdTypeComparator>;
// A set of add and remove commands. IDs should be unique in the two sets.
struct Commands {
Commands();
Commands(const Commands&);
Commands& operator=(const Commands&);
~Commands();
IdSet adds;
IdSet removes;
};
PdfInkUndoRedoModel();
PdfInkUndoRedoModel(const PdfInkUndoRedoModel&) = delete;
PdfInkUndoRedoModel& operator=(const PdfInkUndoRedoModel&) = delete;
~PdfInkUndoRedoModel();
// For `Start()`, `Add()`, `Remove()`, and `Finish()`:
// - The expected usage is: 1 `Start()` call, any number of `Add()` or
// `Remove()` calls, and 1 `Finish()` call.
// - `Start()` 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.
// - `Add()`, `Remove()`, and `Finish()` 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 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 will never
// return an `InkModeledShapeId` which is preexisting and cannot be discarded.
// Must be called before Add() or Remove().
// Must not be called while an operation has already started.
[[nodiscard]] base::expected<std::optional<IdType>, std::monostate> Start();
// Records adding an annotation identified by `id`.
// Must be called between Start() and Finish().
// 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);
// Records erasing an annotation identified by `id`.
// Must be called between Start() and Finish().
// `id` must not be in any `Commands::removes` on the commands stack.
// `id` must not be in `Commands::adds` of the currently active commands.
// If `id` is for a stroke or text, it must be in a `Commands::adds` 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 commands and pushes a new element onto the stack. Must
// be called after Start().
[[nodiscard]] bool Finish();
// Returns the text ID to use when creating or restoring a text annotation to
// satisfy an undo/redo command.
//
// GetRedoInkTextId() only returns `InkTextId` because `InkLoadedTextId`
// is never added to `Commands::adds` in `commands_stack_`.
[[nodiscard]] std::optional<TextId> GetUndoTextId() const;
[[nodiscard]] std::optional<InkTextId> GetRedoInkTextId() const;
// 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();
private:
bool HasIdInPreviousAddCommands(IdType id) const;
bool HasIdInRemoveCommands(IdType id) const;
// Invariants:
// (1) IDs used in `Commands::adds` are unique among all `Commands::adds`
// elements.
// (2) IDs in `Commands::adds` must not exist in any `Commands::removes`.
// (3) IDs used in `Commands::removes` are unique among all
// `Commands::removes` elements.
// (4) IDs added to a `Commands::removes` must exist in the `Commands::adds`
// set of a different `Commands` in the stack.
// Exception: `InkModeledShapeId` and `InkLoadedTextId` because they
// represent pre-existing annotations loaded from the PDF.
// (5) `Commands::adds` only contains `InkStrokeId` and `InkTextId` elements
// here. The reason `Commands::adds` can hold `InkModeledShapeId` and
// `InkLoadedTextId` is to undo their removal, where the caller needs to
// know they need to draw the shape or restore the text annotation.
std::vector<Commands> commands_stack_;
// Invariants:
// (6) Always less than or equal to the size of `commands_stack_`.
// When `has_started_` is true, it is strictly less than the size.
size_t stack_position_ = 0;
// Whether a recording session is currently in progress.
bool has_started_ = false;
};
} // namespace chrome_pdf
#endif // PDF_PDF_INK_UNDO_REDO_MODEL_H_