跳到内容
  • 福昕首页
  • 开发中心
  • SDK文档资料
  • 插件商店
  • 福昕首页
  • 开发中心
  • SDK文档资料
  • 插件商店
申请试用
  • 企业自动化
    • Compressor
  • 福昕CloudAPI
  • 福昕PDF SDK 软件开发工具包
    • 福昕PDF SDK(ActiveX)
    • 福昕PDF SDK(桌面/服务器)
    • 福昕PDF SDK(Plug-in)
    • 福昕 PDF SDK(安卓)
    • 福昕PDF SDK(iOS)
    • 福昕PDF SDK(Web)
  • 福昕管理控制台
    • 公有云
    • 私有云
    • 通用情况
  • 福昕阅读器
    • RMS插件
  • 福昕高级编辑器
    • AI助手
    • Mac版本
      • 常规问题
    • Windows版本
      • 表单
      • ECM集成
      • 互联PDF
      • 企业管理指南
      • 保护
      • 内容编辑
      • 创建PDF
      • 压缩
      • 图章
      • 安装与卸载
      • 常见问题
      • 打印
      • 注释/评论
      • 福昕插件
      • 翻译助手
      • 翻译助手教程
      • 试用与激活
      • 转换
      • 页面管理
    • 教育用户
      • 论文查重
      • 论文畅
      • 操作指南
    • 网页版
      • Foxit eSign
      • 电子签章
    • 订阅
    • 资源
  • 福昕高级编辑器Linux版本
  • 福船图纸管理系统
  • 福昕PDF SDK 软件开发工具包 > 福昕PDF SDK(Web)
  • 标签:
  • GoToR,Launch,PDFViewCtrl,UIExtension,书签

WebSDK 创建书签并设置 GoToR / Launch 动作(打开另一PDF指定页 / 打开非PDF文件)

  • 福昕知识库
  • 2026-09-20

适用版本:福昕 PDF SDK for Web 11.1(PDFViewCtrl / UIExtension 两种初始化模式均适用)

本文所有接口写法与行为均经 11.1 实机验证(含保存回读与 PDF 字节级核对)。

1. 需求与总体思路

需求:在线预览 PDF 时,通过代码创建「带各种属性的书签」,并把书签动作设置为:

  1. GoToR(远程跳转):点击书签打开另一个 PDF 的指定页面位置;
  2. Launch(启动外部文件):点击书签打开一个非 PDF 文件(.txt / .docx / 任意文件)。

WebSDK 的书签能力分两层,选错层是此类需求最常见的失败原因:

层 入口 能力 是否支持 GoToR/Launch
UI 书签面板层 pdfui.getBookmarkDataService().addBookmark(options) 仅文档内跳页(TypeGoto),AddBookmarkOptions = {color?, destId?, destination?, relationship, style?, title?},没有 action 字段 ❌ 不支持
核心书签 API 层 await (await docRender.getPDFDoc()).getBookmarkAPI() 书签树增删改、任意 PDF 动作(TypeGoto / TypeGoToR / TypeGoToE / TypeLaunch / TypeURI / TypeJavaScript / …)、属性、保存 ✅ 必须用这层

两层可混用:核心层创建的带动作书签会正常显示在 UI 书签面板里。

2. 核心访问路径(11.1 实测)

// —— 裸 PDFViewCtrl 模式 ——
const docRender = pdfViewer.getPDFDocRender();        // 同步返回
const pdfDoc    = await docRender.getPDFDoc();        // 主线程核心 PDFDoc
const bapi      = await pdfDoc.getBookmarkAPI();      // ★ 文档级书签 API

// —— UIExtension(PDFUI)模式 ——
const viewer   = await pdfui.getPDFViewer();
const pdfDoc   = await viewer.getPDFDocRender().getPDFDoc();
const bapi     = await pdfDoc.getBookmarkAPI();

注意:不要依赖 pdfDoc.getRootBookmark() / createRootBookmark() —— 11.1 主线程上这两个方法不存在(仅内部链路使用)。一切通过 getBookmarkAPI() 完成。

3. 最小完整代码

3.1 创建 GoToR 书签(打开另一个 PDF 的指定页)

async function createGoToRBookmark(bapi) {
  // 1) 插入书签(destination 必填;插入到根时 relationship 只能 0=FIRST_CHILD 或 1=LAST_CHILD)
  const id = await bapi.insertBookmarkTree({
    relationship: 0,                                     // 0=根下第一个孩子
    title: '打开另一PDF:target.pdf 第3页',
    destination: { pageIndex: 0, zoomMode: 'ZoomFitPage' } // 占位目标,稍后被 GoToR 覆盖
  });

  // 2) 设置属性:颜色走 setProperties(支持 '#RRGGBB'),粗体 style=2(1=斜体,可组合)
  await bapi.setProperties(id, { color: '#FF0000', style: 2 });

  // 3) 关键:插入时 SDK 自动挂了一个 TypeGoto 动作,必须先移除,
  //    否则新动作会被挂成旧动作的子动作(/Next)而不是替换
  await bapi.removeAction(id);

  // 4) 写入 GoToR 动作:打开 target.pdf 第 3 页(pageIndex 为目标文档的 0 基页码)
  await bapi.setAction(id, {
    type: 'TypeGoToR',
    destination: { pageIndex: 2, zoomMode: 'ZoomFitPage' },  // ZoomXYZ 时可加 left/top/zoomFactor
    fileSpec: { fileName: 'target.pdf' },                    // 目标 PDF 路径/URL;不要传 description
    newWindowFlag: 1                                         // 0=False, 1=True, 2=None
  });

  // 5) 回读确认(可选)
  const action = await bapi.getAction(id);
  console.log(action.exportToJson());
  // → { type:'TypeGoToR', destination:{pageIndex:2, zoomMode:'ZoomFitPage'},
  //     newWindowFlag:1, fileSpec:{fileName:'target.pdf'} }
  return id;
}

保存后 PDF 内部对象(字节级核对一致):

书签对象:  <</A 28 0 R /C[1 0 0] /Title(…) /Parent … /Count …>>
动作对象:  <</NewWindow true /S/GoToR /D[2/Fit] /F 29 0 R /Type/Action>>
FileSpec:  <</UF(target.pdf) /F(target.pdf) /Type/Filespec>>

3.2 创建 Launch 书签(打开非 PDF 文件)

async function createLaunchBookmark(bapi) {
  const id = await bapi.insertBookmarkTree({
    destId: undefined, relationship: 1,                  // 1=LAST_CHILD,插到根末尾
    title: '打开附件:note.txt',
    destination: { pageIndex: 0, zoomMode: 'ZoomFitPage' }
  });
  await bapi.setProperties(id, { color: '#0000FF', style: 1 });   // 1=斜体
  await bapi.removeAction(id);                                     // 同样先清自动生成的 TypeGoto

  await bapi.setAction(id, {
    type: 'TypeLaunch',
    fileSpec: { fileName: 'note.txt' }                   // 非 PDF 文件(.txt/.docx/…均可)
  });
  return id;
}

保存后 PDF 内部对象:<</S/Launch /F 32 0 R /Type/Action>> + <</UF(note.txt) /F(note.txt) /Type/Filespec>>。

3.3 创建多级书签树(带各种属性)

// 根级第 1 个:FIRST_CHILD;之后逐个用 destId=前一个 + NEXT_SIBLING(3) 串成兄弟链
const id1 = await bapi.insertBookmarkTree({
  relationship: 0, title: '1. 打开另一PDF:target.pdf 第3页',
  destination: { pageIndex: 0, zoomMode: 'ZoomFitPage' }, style: 2 });
await bapi.setProperties(id1, { color: '#FF0000' });

const id2 = await bapi.insertBookmarkTree({
  destId: id1, relationship: 3,                          // 3=NEXT_SIBLING(id1 的下一个兄弟)
  title: '2. 打开非PDF文件:note.txt',
  destination: { pageIndex: 0, zoomMode: 'ZoomFitPage' }, style: 1 });
await bapi.setProperties(id2, { color: '#0000FF' });

// 二级子书签:destId=父id + FIRST_CHILD(0)
const id11 = await bapi.insertBookmarkTree({
  destId: id1, relationship: 0, title: '1.1 子书签(文档内第2页)',
  destination: { pageIndex: 1, zoomMode: 'ZoomFitPage' } });

// 整树回读:[{id, deep, title, color, isBold, isItalic, children:[…]}]
const tree = await pdfDoc.getBookmarksJson();

relationship 全表:0=FIRST_CHILD, 1=LAST_CHILD, 2=PREVIOUS_SIBLING, 3=NEXT_SIBLING, 4=FIRST_SIBLING, 5=LAST_SIBLING(destId 省略即插入根时只允许 0/1)。

3.4 保存

const data = await pdfDoc.getFile({ flags: 0, fileName: 'output.pdf' });  // 返回 Blob
// 例:上传回服务端
const bytes = new Uint8Array(await data.arrayBuffer());
await fetch('/save?name=output.pdf', { method: 'POST', body: bytes });

保存 → 重新打开后,getAction() 回读的动作类型/目标页/文件名与写入完全一致(实机两轮验证)。

4. 必须注意的 6 个坑(全部实测踩过)

  1. UI 面板 addBookmark 做不到任意动作:AddBookmarkOptions 无 action 字段,创建的书签固定为 TypeGoto(还会自动带当前视口位置)。要 GoToR/Launch 必须走 getBookmarkAPI()。
  2. 先 removeAction 再 setAction:插入书签时 SDK 自动挂 TypeGoto;直接 setAction 会把新动作追加为子动作(/Next)而非替换,回读仍是 TypeGoto。
  3. 插入根的 relationship 只能 0/1:否则抛 When inserting a bookmark into the root, the relationship must be either "BookmarkPosition.e_PosFirstChild" or "BookmarkPosition.e_PosLastChild". 根级兄弟用 destId=前一个, relationship:3。
  4. 颜色用 setProperties 传:insertBookmarkTree options 里的 color 需要 int,传 '#FF0000' 字符串会落成黑色;setProperties(id,{color:'#FF0000'}) 内部做了转换,字符串最稳妥。style:1=斜体, 2=粗体(可按位组合)。
  5. GoToR 的 fileSpec 不要传 description:11.1 会把 JS 字符串直接传给底层 FileSpec.SetDescription 触发 embind 错误 Cannot pass "…" as a String。另外该 API 写入的 GoToR FileSpec 会附带一个空嵌入式文件流(/EF,SDK 实现行为),阅读器按 /F 路径解析目标,不影响动作合法性。
  6. pageIndex 是目标文档的 0 基页码:destination:{pageIndex:2} 即目标文件第 3 页;校验目标文档页数是应用层职责(SDK 写入时不校验目标文件)。

5. 浏览器端执行行为的现实约束

  • GoToR:动作数据合法且已写入 PDF,桌面阅读器(福昕桌面版/Acrobat)可直接执行。Web 端点击书签后的行为取决于阅读器实现与应用集成——浏览器沙箱内”打开另一本地文件”天然受限。eCTD 等 B/S 场景建议:应用层监听书签点击/动作执行,按 fileSpec 自行加载对应文档(WebSDK 提供 ActionCallbackManager.setEmbeddedGotoCallback 一类回调出口,11.1 运行库实测存在)。
  • Launch:普通 http 页面无法静默启动本地程序打开非 PDF 文件,浏览器会阻止或转为下载,且通常需要真实用户点按。动作合法性体现在 PDF 内容层(写入正确、可回读、桌面端可执行)。Web 应用更常见的等效做法是:监听点击后由应用层处理(下载、新窗口、或调用在线预览组件打开 .docx 等)。
  • UI 书签面板与核心层共用同一书签存储(BookmarkDataService.reloadBookmarkChildren() 可刷新面板数据),核心层创建的书签(标题/颜色/样式/层级)在面板中可见可点;BookmarkDataService.performAction(id) 可编程触发动作执行(实测调用无异常;浏览器对跨文档/外部文件的实际打开受安全策略约束,见上两条)。

6. 一页速查(API 速览)

目的 调用
拿书签 API (await viewer.getPDFDocRender().getPDFDoc()).getBookmarkAPI()
插入书签 bapi.insertBookmarkTree({destId?, relationship, title, destination, style?}) → Promise<id>
改属性 bapi.setProperties(id, {title?, color?, style?})
读属性 bapi.getProperties(id)
清动作 bapi.removeAction(id)
设 GoToR bapi.setAction(id, {type:'TypeGoToR', destination:{pageIndex, zoomMode}, fileSpec:{fileName}, newWindowFlag})
设 Launch bapi.setAction(id, {type:'TypeLaunch', fileSpec:{fileName}})
读动作 bapi.getAction(id) → 对象,.exportToJson() 序列化
整树回读 pdfDoc.getBookmarksJson()
删书签/移动 bapi.remove(id) / bapi.moveTo({srcId, destId, relationship})
保存 pdfDoc.getFile({flags:0, fileName}) → Blob

(以上均为 WebSDK 11.1 实机验证过的写法;getBookmarkAPI/insertBookmarkTree/setAction/... 未出现在公开 API 文档与声明文件中,属于运行库实机验证过的可用接口,跨大版本升级时建议做回归验证。)

相关内容

WebSDK 判断 PDF 是否被数字签名限制编辑,并转为可在线编辑的 PDF(基于 UIExtension)

如何启用/禁用多个福昕高级PDF 编辑器的显示窗口?

如何在Windows上使用福昕小工具(FoxitInfoCollectTool.exe)收集福昕高级PDF编辑器的日志?

如何在Microsoft Edge浏览器中启用 IE 模式?

如何解决福昕高级PDF编辑器外接显示器打印对话框窗口显示问题

如何用命令行或GPO部署安装福昕并同步激活程序?

如果需要比对同类型文档,或是一份文档的新旧稿件,有什么好方法?

奇数页和偶数页的文档如何合并成一个文档?

通过账号登录授权提示err8001怎么办?

如何给多个文档添加连续且唯一性的贝茨编号?

推荐内容

WebSDK 判断 PDF 是否被数字签名限制编辑,并转为可在线编辑的 PDF(基于 UIExtension)

如何启用/禁用多个福昕高级PDF 编辑器的显示窗口?

WebSDK 创建书签并设置 GoToR / Launch 动作(打开另一PDF指定页 / 打开非PDF文件)

如何在Windows上使用福昕小工具(FoxitInfoCollectTool.exe)收集福昕高级PDF编辑器的日志?

如何在Microsoft Edge浏览器中启用 IE 模式?

如何解决福昕高级PDF编辑器外接显示器打印对话框窗口显示问题

如何用命令行或GPO部署安装福昕并同步激活程序?

如果需要比对同类型文档,或是一份文档的新旧稿件,有什么好方法?

奇数页和偶数页的文档如何合并成一个文档?

通过账号登录授权提示err8001怎么办?

产品
  • 应用行业
  • 白皮书
开发支持
  • 开发中心
  • SDK文档资料

销售咨询:010-50951668

客服电话:0591-38509808

销售咨询
微信公众号

©2026 福建福昕软件开发股份有限公司 版权所有

隐私策略