OFD 注释与动作
本节介绍 OFD 页面注释(Annot)的访问、创建与属性设置,以及注释与页面关联的动作(Action 及其派生类)配置。取得 OFDPage 的方式请参阅 OFD 文档与页面。
任务场景
- 枚举页面注释,读取类型、边界框、外观与可见性等属性。
- 创建或修改链接、高亮、路径、签章(
e_AnnotTypeStamp)等注释,并通过SetCreator记录创建者信息。 - 配置页内跳转(
GoToAction)、外部 URI(URIAction)或音视频(SoundAction/MovieAction)。 - 通过
OFDDoc::ExportAnnots/ImportAnnots或AnnotCombination批量迁移批注数据。
API 概览
| 功能 | C++ API(foxit::ofd) | 核心参数 / 描述 |
|---|---|---|
| 注释对象 | Annot | GetType、GetBoundary、GetAppearanceObject;支持链接、高亮、路径、签章、水印、控件等类型 |
| 页面注释管理 | OFDPage::GetAnnotCount、GetAnnot、AddAnnot、RemoveAnnot | AddAnnot(AnnotType) 创建指定类型注释 |
| 动作基类 | Action | GetType、GetEventTrigger、SetEventTrigger;触发时机含文档打开、页面打开、鼠标点击 |
| 动作集合 | Actions | GetActionCount、GetAction、AddAction、RemoveAction;可挂于 Annot、OFDPage 或图元对象 |
| 跳转动作 | GoToAction | SetDestination(x, y, page_id) 设置目标页与坐标;GetDestination 返回 ActionDestinationInfo(含 XYZ、Fit、FitH 等视图模式) |
| URI 动作 | URIAction | SetURI / GetURI 设置外部链接 |
| 声音动作 | SoundAction | SetResourceID、SetVolume、EnableRepeat、EnableSynchronous |
| 视频动作 | MovieAction | SetResourceID、SetOperator(播放 / 停止 / 暂停 / 继续) |
| 注释组合 | AnnotCombination | AddAnnotFile、ExportToFile、ExportAnnotFile;支持文件路径或内存流 |
| 文档级批注迁移 | OFDDoc::ExportAnnots、ImportAnnots | 按页范围导出或导入批注文件 |
访问页面注释
通过 OFDPage::GetAnnotCount 与 GetAnnot 遍历页面注释,读取类型与外接矩形。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 设置热区,SetCreator、EnableVisible、EnablePrintable、EnableReadOnly 等接口控制元数据与状态。链接注释可调用 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());
}
}
配置动作响应
动作定义用户与注释(或页面、图元)交互时的行为。典型流程:
- 通过
Annot::GetActions、OFDPage::GetActions或图元对象的CreateActions/GetActions取得Actions集合。 - 调用
Actions::AddAction指定动作类型(如Action::e_ActionTypeGoto、e_ActionTypeUri)。 - 将返回的
Action转为派生类(GoToAction、URIAction等),设置参数与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");
}
}
}
其他动作类型
- 多媒体:
SoundAction、MovieAction通过SetResourceID引用文档资源库中的音频/视频,SetVolume、SetOperator等控制播放行为。
批注数据迁移
文档级迁移可使用 OFDDoc::ExportAnnots 将指定页范围批注导出为独立文件,再通过 ImportAnnots 导入目标文档。跨文件合并多个批注包时,可使用 AnnotCombination::AddAnnotFile 聚合后 ExportToFile 或 ExportAnnotFile 输出。
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。 - 资源依赖:
SoundAction、MovieAction依赖SetResourceID所引用的资源 ID,须先将音频或视频加入OFDDoc资源库后再绑定动作。 - 类型匹配:从
Action构造GoToAction、URIAction等派生类时,动作类型必须匹配,否则将抛出异常。 - 持久化:对
Annot或Action的修改均为内存操作,须调用OFDPackage::Save或SaveAs写回文件,请参阅 OFD 文件包。 - 与 PDF 模块的差异:OFD 动作类型与 PDF 模块不完全相同,请以 OFD 接口文档为准。