Skip to content

OFD 注释与动作

本节介绍 OFD 页面注释(Annot)的访问、创建与属性设置,以及注释与页面关联的动作(Action 及其派生类)配置。取得 OFDPage 的方式请参阅 OFD 文档与页面

任务场景

  • 枚举页面注释,读取类型、边界框、外观与可见性等属性。
  • 创建或修改链接、高亮、路径、签章(e_AnnotTypeStamp)等注释,并通过 SetCreator 记录创建者信息。
  • 配置页内跳转(GoToAction)、外部 URI(URIAction)或音视频(SoundAction / MovieAction)。
  • 通过 OFDDoc::ExportAnnots / ImportAnnotsAnnotCombination 批量迁移批注数据。

API 概览

功能C++ API(foxit::ofd核心参数 / 描述
注释对象AnnotGetTypeGetBoundaryGetAppearanceObject;支持链接、高亮、路径、签章、水印、控件等类型
页面注释管理OFDPage::GetAnnotCount
GetAnnotAddAnnotRemoveAnnot
AddAnnot(AnnotType) 创建指定类型注释
动作基类ActionGetTypeGetEventTriggerSetEventTrigger;触发时机含文档打开、页面打开、鼠标点击
动作集合ActionsGetActionCountGetActionAddActionRemoveAction;可挂于 AnnotOFDPage 或图元对象
跳转动作GoToActionSetDestination(x, y, page_id) 设置目标页与坐标;GetDestination 返回 ActionDestinationInfo(含 XYZ、Fit、FitH 等视图模式)
URI 动作URIActionSetURI / GetURI 设置外部链接
声音动作SoundActionSetResourceIDSetVolumeEnableRepeatEnableSynchronous
视频动作MovieActionSetResourceIDSetOperator(播放 / 停止 / 暂停 / 继续)
注释组合AnnotCombinationAddAnnotFileExportToFileExportAnnotFile;支持文件路径或内存流
文档级批注迁移OFDDoc::ExportAnnotsImportAnnots按页范围导出或导入批注文件

访问页面注释

通过 OFDPage::GetAnnotCountGetAnnot 遍历页面注释,读取类型与外接矩形。GetBoundary 返回的矩形基于 OFD 页面坐标系,UI 绘制或坐标转换时需结合页面显示矩阵,请参阅 OFD 渲染

渲染时可通过 OFDRender 的内容标志控制是否绘制注释层。

c++
#include "ofd/fs_ofdpackage.h"
#include "ofd/fs_ofddoc.h"
#include "ofd/fs_ofdpage.h"
#include "ofd/annots/fs_ofdannot.h"

using namespace foxit;
using namespace foxit::ofd;

void ReadPageAnnots(const wchar_t* input_file) {
  OFDPackage package(input_file);
  if (package.IsEmpty() || package.GetDocumentCount() <= 0) {
    return;
  }

  OFDDoc doc = package.LoadDocument(0, L"");
  if (doc.IsEmpty() || doc.GetPageCount() <= 0) {
    return;
  }

  OFDPage page = doc.GetPage(0);
  if (page.IsEmpty()) {
    return;
  }

  int annot_count = page.GetAnnotCount();
  for (int i = 0; i < annot_count; ++i) {
    Annot annot = page.GetAnnot(i);
    if (annot.IsEmpty()) {
      continue;
    }

    Annot::AnnotType type = annot.GetType();
    RectF boundary = annot.GetBoundary();
    String creator = annot.GetCreator();
  }
}
java
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.ofd.OFDDoc;
import com.foxit.sdk.ofd.OFDPackage;
import com.foxit.sdk.ofd.OFDPage;
import com.foxit.sdk.ofd.annots.Annot;

public class OFDAnnotReadExample {
    public static void readPageAnnots(String inputFile) throws Exception {
        OFDPackage pkg = new OFDPackage(inputFile);
        if (pkg.isEmpty() || pkg.getDocumentCount() <= 0) {
            return;
        }

        OFDDoc doc = pkg.loadDocument(0, null);
        if (doc.isEmpty() || doc.getPageCount() <= 0) {
            return;
        }

        OFDPage page = doc.getPage(0);
        if (page.isEmpty()) {
            return;
        }

        int annotCount = page.getAnnotCount();
        for (int i = 0; i < annotCount; i++) {
            Annot annot = page.getAnnot(i);
            if (annot.isEmpty()) {
                continue;
            }

            int type = annot.getType();
            RectF boundary = annot.getBoundary();
            String creator = annot.getCreator();
        }
    }
}

创建与配置注释

调用 OFDPage::AddAnnot 创建指定类型的注释,再通过 SetBoundary 设置热区,SetCreatorEnableVisibleEnablePrintableEnableReadOnly 等接口控制元数据与状态。链接注释可调用 SetLinkUri 设置 URI(SDK 会同步关联 URI 动作)。

签章场景可使用 e_AnnotTypeStamp,配合 AddAppearanceObject 设置签章外观;高亮注释可通过外观路径图元定义填充区域。

c++
#include "ofd/fs_ofdpackage.h"
#include "ofd/fs_ofddoc.h"
#include "ofd/fs_ofdpage.h"
#include "ofd/annots/fs_ofdannot.h"

using namespace foxit;
using namespace foxit::ofd;

bool CreateLinkAnnot(OFDPage& page) {
  if (page.IsEmpty()) {
    return false;
  }

  Annot annot = page.AddAnnot(Annot::e_AnnotTypeLink);
  if (annot.IsEmpty()) {
    return false;
  }

  annot.SetBoundary(RectF(20.0f, 180.0f, 80.0f, 140.0f));
  annot.SetSubtype("Link");
  annot.SetCreator("DemoUser");
  annot.SetLinkUri("https://www.example.com");
  annot.EnableVisible(true);
  annot.EnablePrintable(true);

  Actions actions = annot.GetActions();
  if (actions.IsEmpty() || actions.GetActionCount() <= 0) {
    return false;
  }

  URIAction uri_action(actions.GetAction(0));
  return !uri_action.IsEmpty() && uri_action.GetURI() == "https://www.example.com";
}
java
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.ofd.Action;
import com.foxit.sdk.ofd.Actions;
import com.foxit.sdk.ofd.OFDPage;
import com.foxit.sdk.ofd.URIAction;
import com.foxit.sdk.ofd.annots.Annot;

public class OFDAnnotCreateExample {
    public static boolean createLinkAnnot(OFDPage page) throws Exception {
        if (page.isEmpty()) {
            return false;
        }

        Annot annot = page.addAnnot(Annot.e_AnnotTypeLink);
        if (annot.isEmpty()) {
            return false;
        }

        annot.setBoundary(new RectF(20.0f, 180.0f, 80.0f, 140.0f));
        annot.setSubtype("Link");
        annot.setCreator("DemoUser");
        annot.setLinkUri("https://www.example.com");
        annot.enableVisible(true);
        annot.enablePrintable(true);

        Actions actions = annot.getActions();
        if (actions.isEmpty() || actions.getActionCount() <= 0) {
            return false;
        }

        URIAction uriAction = new URIAction(actions.getAction(0));
        return !uriAction.isEmpty()
                && "https://www.example.com".equals(uriAction.getURI());
    }
}

配置动作响应

动作定义用户与注释(或页面、图元)交互时的行为。典型流程:

  1. 通过 Annot::GetActionsOFDPage::GetActions 或图元对象的 CreateActions / GetActions 取得 Actions 集合。
  2. 调用 Actions::AddAction 指定动作类型(如 Action::e_ActionTypeGotoe_ActionTypeUri)。
  3. 将返回的 Action 转为派生类(GoToActionURIAction 等),设置参数与 SetEventTrigger
c++
#include "ofd/fs_ofdpage.h"
#include "ofd/fs_ofdaction.h"

using namespace foxit::ofd;

void ConfigurePageActions(OFDPage& page) {
  if (page.IsEmpty()) {
    return;
  }

  Actions actions = page.GetActions();
  if (actions.IsEmpty()) {
    return;
  }

  GoToAction goto_action(actions.AddAction(Action::e_ActionTypeGoto));
  if (!goto_action.IsEmpty()) {
    goto_action.SetEventTrigger(Action::e_ActionEventTriggerTypePageOpen);
    goto_action.SetDestination(12.5f, 34.5f, page.GetID());
  }

  URIAction uri_action(actions.AddAction(Action::e_ActionTypeUri));
  if (!uri_action.IsEmpty()) {
    uri_action.SetEventTrigger(Action::e_ActionEventTriggerTypeClick);
    uri_action.SetURI("https://www.example.com");
  }
}
java
import com.foxit.sdk.ofd.Action;
import com.foxit.sdk.ofd.Actions;
import com.foxit.sdk.ofd.GoToAction;
import com.foxit.sdk.ofd.OFDPage;
import com.foxit.sdk.ofd.URIAction;

public class OFDActionExample {
    public static void configurePageActions(OFDPage page) throws Exception {
        if (page.isEmpty()) {
            return;
        }

        Actions actions = page.getActions();
        if (actions.isEmpty()) {
            return;
        }

        GoToAction gotoAction = new GoToAction(
                actions.addAction(Action.e_ActionTypeGoto));
        if (!gotoAction.isEmpty()) {
            gotoAction.setEventTrigger(Action.e_ActionEventTriggerTypePageOpen);
            gotoAction.setDestination(12.5f, 34.5f, page.getID());
        }

        URIAction uriAction = new URIAction(
                actions.addAction(Action.e_ActionTypeUri));
        if (!uriAction.isEmpty()) {
            uriAction.setEventTrigger(Action.e_ActionEventTriggerTypeClick);
            uriAction.setURI("https://www.example.com");
        }
    }
}

其他动作类型

  • 多媒体SoundActionMovieAction 通过 SetResourceID 引用文档资源库中的音频/视频,SetVolumeSetOperator 等控制播放行为。

批注数据迁移

文档级迁移可使用 OFDDoc::ExportAnnots 将指定页范围批注导出为独立文件,再通过 ImportAnnots 导入目标文档。跨文件合并多个批注包时,可使用 AnnotCombination::AddAnnotFile 聚合后 ExportToFileExportAnnotFile 输出。

c++
#include "ofd/fs_ofddoc.h"
#include "ofd/annots/fs_ofdannot.h"

using namespace foxit::ofd;

void ExportDocumentAnnots(OFDDoc& doc, const char* export_path) {
  if (doc.IsEmpty() || doc.GetPageCount() <= 0) {
    return;
  }

  doc.ExportAnnots(0, doc.GetPageCount() - 1, export_path);
}

bool MergeAnnotFiles(const char* output_path) {
  AnnotCombination combination;
  if (combination.IsEmpty()) {
    return false;
  }

  if (!combination.AddAnnotFile("annot_pack_1.ofd")) {
    return false;
  }
  if (!combination.AddAnnotFile("annot_pack_2.ofd")) {
    return false;
  }

  return combination.ExportToFile(output_path);
}
java
import com.foxit.sdk.ofd.OFDDoc;
import com.foxit.sdk.ofd.annots.AnnotCombination;

public class OFDAnnotMigrationExample {
    public static void exportDocumentAnnots(OFDDoc doc, String exportPath) throws Exception {
        if (doc.isEmpty() || doc.getPageCount() <= 0) {
            return;
        }

        doc.exportAnnots(0, doc.getPageCount() - 1, exportPath);
    }

    public static boolean mergeAnnotFiles(String outputPath) throws Exception {
        AnnotCombination combination = new AnnotCombination();
        if (combination.isEmpty()) {
            return false;
        }

        if (!combination.addAnnotFile("annot_pack_1.ofd")) {
            return false;
        }
        if (!combination.addAnnotFile("annot_pack_2.ofd")) {
            return false;
        }

        return combination.exportToFile(outputPath);
    }
}

注意事项

  • 坐标系GetBoundary 与动作目标坐标均基于 OFD 页面坐标系,与屏幕/UI 坐标转换时请使用 OFDPage::GetDisplayMatrix
  • 资源依赖SoundActionMovieAction 依赖 SetResourceID 所引用的资源 ID,须先将音频或视频加入 OFDDoc 资源库后再绑定动作。
  • 类型匹配:从 Action 构造 GoToActionURIAction 等派生类时,动作类型必须匹配,否则将抛出异常。
  • 持久化:对 AnnotAction 的修改均为内存操作,须调用 OFDPackage::SaveSaveAs 写回文件,请参阅 OFD 文件包
  • 与 PDF 模块的差异:OFD 动作类型与 PDF 模块不完全相同,请以 OFD 接口文档为准。