# # Undo/Redo

## # 简介

Foxit PDF SDK for Web 提供了一个名为 "undo-redo" 的插件，它可以为 PDF 文档添加撤销和重做功能。用户可以通过 `Ctrl+Z` 和 `Ctrl+Y` 组合键来撤销和重做以下操作：

- 添加注释
- 删除注释
- 修改注释
- 等等

此外，该插件还提供了编程接口，允许开发者在自定义应用中实现撤销和重做功能。

## # 加载插件

请参考 Addons 简介 章节中的方法。插件加载后， undo-redo 功能便会自动生效。用户可以通过 `Ctrl+Z` 和 `Ctrl+Y` (Mac 中为 `Cmd+Z` 和 `Cmd+Y`)来撤销和重做上一步操作。

## # 编程接口

除了提供快捷键之外，undo-redo 插件还提供了一系列编程接口帮助开发者实现自定义功能。在调用接口之前，需要先获取 undo-redo 插件的对象实例：

    async function obtainAddonInstanceExample() {
        const undoRedoAddon = await pdfui.getAddonInstance("UndoRedoAddon");
        if (undoRedoAddon) {
            // Here you can begin to use the methods of undoRedoAddon object.
        } else {
            console.error("UndoRedo Addon instance not found.");
        }
    }

关于 UndoRedoAddon 详细的接口描述，可以参考 API Reference
(opens new window)。

获取到 UndoRedoAddon 实例后，接下来开始使用它。

### # 撤销操作

`undoRedoAddon.undo()` 接口用于撤销操作，它可以撤销用户的最后一次操作。示例如下：

    async function undoExample() {
        const undoRedoAddon = await pdfui.getAddonInstance("UndoRedoAddon");
        if (undoRedoAddon) {
            undoRedoAddon.undo();
        } else {
            console.error("UndoRedo Addon instance not found.");
        }
    }

### # 重做操作

`undoRedoAddon.redo()` 接口用于重做操作，它可以重新执行最后一次被撤销的用户操作。示例如下：

    async function redoExample() {
        const undoRedoAddon = await pdfui.getAddonInstance("UndoRedoAddon");
        if (undoRedoAddon) {
            undoRedoAddon.redo();
        } else {
            console.error("UndoRedo Addon instance not found.");
        }
    }

### # 撤销所有操作

`undoRedoAddon.undoAll()` 接口用于撤销所有操作，它可以撤销用户所有的操作，将文档还原到最初状态。示例如下：

    async function undoAllExample() {
        const undoRedoAddon = await pdfui.getAddonInstance("UndoRedoAddon");
        if (undoRedoAddon) {
            undoRedoAddon.undoAll();
        } else {
            console.error("UndoRedo Addon instance not found.");
        }
    }

虽然所有操作被撤销，但是也依然可以一步一步地重做。

### # 记录操作

UndoRedoAddon 还提供了一个 `invoke` 方法，可以通过该方法传递回调函数。该函数接受一个 PDFDoc
(opens new window) 对象，该对象是一个被包装过的对象。相对于未包装过的 PDFDoc ，其不同之处在于可以记录操作，这些被记录的操作可以被撤销和重做。

下面是一个简单的示例，在回调函数中记录了创建注释和修改注释的操作。这些操作被记录后，可以通过 `undo/redo` 接口来撤销和重做：

    function wait() {
        // 这个函数的作用是让下面的每个步骤执行完后停顿一定时间，方便查看效果。
        return new Promise((resolve) => setTimeout(resolve, 2000));
    }
    async function invokeExample() {
        const undoRedoAddon = await pdfui.getAddonInstance("UndoRedoAddon");
        if (undoRedoAddon) {
            await undoRedoAddon.invoke(async (pdfDoc) => {
                const pdfPage = await pdfDoc.getPageByIndex(0);
                const [square, popup] = await pdfPage.addAnnot({
                    type: "square",
                    rect: {
                        left: 0,
                        right: 100,
                        bottom: 500,
                        top: 550,
                    },
                    color: 0xff0000, // 初始化外边框颜色为：红色
                });
                await wait();
                // 创建完成后，将外边框颜色设置为紫色
                await square.setBorderColor(0xff00ff);
            });

            await wait();

            // 撤销后，颜色变回红色
            await undoRedoAddon.undo();

            await wait();

            // 重做后，颜色变为紫色
            await undoRedoAddon.redo();

            await wait();

            // 撤销所有，square Annotation 将被删除
            await undoRedoAddon.undoAll();
        } else {
            console.error("UndoRedo Addon instance not found.");
        }
    }

## # 支持 Undo/Redo 的 API 列表

下面列举了 Foxit PDF SDK for Web 当前支持撤销和重做操作的 API。

### # 支持的对象获取接口

这里列举了一系列接口，当在 `UIXAddon.invoke` 回调函数中被调用时，会返回一个被包装过的对象。开发者可以调用这个被包装过的对象的方法来记录操作：

1. PDFDoc
   (opens new window) 接口

   - `PDFDoc.getPageByIndex(index)`: 通过索引获取文档中的页面。
   - `PDFDoc.getPageById(id)`: 通过页面的唯一标识符获取页面。
   - `PDFDoc.getAnnots()`: 获取文档中的所有注释。

2. PDFPage
   (opens new window) 接口

   - `PDFPage.getAnnots()`: 获取页面上的所有注释。
   - `PDFPage.getAnnotsByObjectNumArray(objectNums)`: 通过对象编号数组获取页面上的注释。
   - `PDFPage.getAnnotsByIdArray(annotIds)`: 通过注释标识符数组获取页面上的注释。
   - `PDFPage.getMarkupAnnots()`: 获取页面上的标记注释。
   - `PDFPage.getAnnotTree()`: 获取页面上注释的树状结构。

示例:

    async function getAnnotsExample() {
        const undoRedoAddon = await pdfui.getAddonInstance("UndoRedoAddon");
        if (undoRedoAddon) {
            await undoRedoAddon.invoke(async (pdfDoc) => {
                const pdfPage = await pdfDoc.getPageByIndex(0);
                const [square] = await pdfPage.getAnnotsByObjectNumArray([12002]);
                square.setBorderColor(0xFF0000);
            });
            await undoRedoAddon.undo();
        } else {
            console.error("UndoRedo Addon instance not found.");
        }
    }

### # 支持的操作接口

这些接口被调用后，其操作将会被自动记录，记录后的操作可以被撤销和重做。要注意的是，这些接口必须在上一段中列举的 对象获取接口 返回的对象上调用。

1. PDFDoc
   (opens new window) 接口
   - `PDFDoc.addAnnot`
2. PDFPage
   (opens new window) 接口
   - `PDFPage.addAnnot`
   - `PDFPage.removeAnnotById`
   - `PDFPage.removeAnnotByObjectNumber`
3. Annot
   (opens new window) 接口 (所有注释类的超类)：
   - `Annot.setContent`
   - `Annot.setRect`
   - `Annot.setBorderColor`
   - `Annot.setBorderInfo`
   - `Annot.setFlags`
4. Markup
   (opens new window) 接口 (所有 Markup 注释的超类, 包括：FreeText (callout, textbox, typewriter), ink, line, note, polygon, polyline, redact, sound, square, stamp and TextMarkup (highlight, squiggly, strikeout, underline)):
   - `Markup.setOpacity`
   - `Markup.setSubject`
   - `Markup.setTitle`
   - `Markup.addReply`
   - `Markup.addReviewState`
   - `Markup.addMarkedState`
   - `Markup.setFillColor`
5. 特定的注释类型：
   - Circle
     (opens new window)
     - `Circle.setMeasureRatio`
     - `Circle.setMeasureUnit`
     - `Circle.setMeasureConversionFactor`
     - `Circle.enableCaption`
     - `Circle.setCaptionColor`
   - Line
     (opens new window)
     - `Line.setMeasureRatio`
     - `Line.setMeasureUnit`
     - `Line.enableCaption`
     - `Line.setCaptionColor`
     - `Line.setEndPoint`
     - `Line.setStartPoint`
     - `Line.setEndingStyle`
     - `Line.setStyleFillColor`
     - `Line.setLeaderLineExtend`
     - `Line.setLeaderLineOffset`
     - `Line.setLeaderLineLength`
     - `Line.setCaptionOffset`
   - Polygon
     (opens new window)
     - `Polygon.enableCaption`
     - `Polygon.setCaptionColor`
     - `Polygon.updateVertexes`
     - `Polygon.setVertexes`
   - Polyline
     (opens new window)
     - `Polyline.setMeasureRatio`
     - `Polyline.enableCaption`
     - `Polyline.setCaptionColor`
     - `Polyline.setEndingStyle`
     - `Polyline.setStyleFillColor`
     - `Polyline.updateVertexes`
     - `Polyline.setVertexes`
   - Square
     (opens new window)
     - `Square.setMeasureRatio`
     - `Square.setMeasureUnit`
     - `Square.setMeasureConversionFactor`
   - FreeText
     (opens new window)(typewriter, textbox, callout)
     - `FreeText.setAlignment`
     - `FreeText.setInnerRect`
     - `FreeText.setCalloutLinePoints`
     - `FreeText.setCalloutLineEndingStyle`
     - `FreeText.setDefaultAppearance`
     - `FreeText.setRotation`
   - Screen
     (opens new window)
     - `Screen.setRotation`
   - Stamp
     (opens new window)
     - `Stamp.setRotation`
   - Redact
     (opens new window)
     - `Redact.setDefaultAppearance`
     - `Redact.setOverlayText`
     - `Redact.removeOvelayText`
     - `Redact.setOverlayTextAlignment`
     - `Redact.setRepeat`
     - `Redact.setAutoFontSize`
     - `Redact.setRedactApplyFillColor`
   - Ink
     (opens new window)
     - `Ink.setInkList`
   - Link
     (opens new window)
     - `Link.setHighlightingMode`
     - `Link.setAction`
   - FileAttachment
     (opens new window)
     - `FileAttachment.setIconName`
   - Note
     (opens new window)
     - `Note.setIconName`