Structure

Structure

本页介绍文档导航和逻辑结构:大纲(书签)、目录、命名目标,以及用于可访问性和重排的标记结构树。


大纲(书签)

Document.GetOutlines() 返回当前的大纲树,作为 OutlineItem[](当不存在 /Outlines 时为 []);Document.SetOutlines() 替换它 — 传入 [] 会完全删除大纲。每个 OutlineItem 都有一个 Title、一个 Dest(页码或命名目标),可选的用于嵌套的 Children,以及样式(Color、Bold、Italic、Open)。

const items: OutlineItem[] = sections.map((s) => {
  const item: OutlineItem = { Title: s.title, Dest: { name: s.dest } };
  if (s.subtype === 'sales') {
    item.Open = true;
    item.Children = ['Pasta', 'Pizza', 'Antipasti']
      .map((cat) => ({ Title: cat, Dest: { name: s.dest } }));
  }
  return item;
});
doc.SetOutlines(items);

目录

Page.AddTOC() 将一系列 TOCEntry 对象渲染为页面上的一个矩形——每个对象都有一个 title 和一个目标 page 编号——并带有换行的标题、点线引导以及右对齐的页码标签。它返回一个 AddTOCResult,报告绘制了多少条目;如果盒子太小,则返回未能容纳的 remainder。

page.AddTOC(
  sections.map((s, i) => ({ title: `${i + 1}. ${s.title}`, page: s.page.Number })),
  [72, 160, 400, 400],
  { font: 'Helvetica', fontSize: 13, rowGap: 18 },
);

命名目标

Document.GetNamedDestinations() 返回每个已命名的目标——合并 /Names /Dests 名称树和传统的 /Dests 字典——以 { name, dest } 对的形式,其中 dest 是一个 PageDest({ page, view })。这些与 OutlineItem.Dest 或链接注释可以通过名称而不是显式页码引用的已命名目标相同。

const destNames = new Set(doc.GetNamedDestinations().map((d) => d.name));
for (const { name, dest } of doc.GetNamedDestinations()) {
  console.log(name, '-> page', dest.page);
}

已标记结构树

Document.AutoTag() 从页面布局推断并创建一个 /StructTreeRoot——通过字体大小聚类识别标题,通过文本块识别段落,通过线条几何识别表格,以及针对每张图片的 /Figure 与 /Artifact 决策——并返回一个包含每种元素计数的 AutoTagReport。Document.GetStructTree() 读取生成的 StructTreeRoot,如果文档未标记则读取 null。

const report = doc.AutoTag({ lang: 'en-US', title: 'Quarterly Report', tables: true });
console.log(report.headings, report.paragraphs, report.tables, report.figures);

const tree = doc.GetStructTree();
console.log(tree ? tree.GetText() : '(no structure tree)');

在 AutoTag() 已执行之后添加的内容——例如之后填充的页面——不在该遍历的覆盖范围内,也不会被双重标记。StructTreeRoot.Append() 和 StructElement.MarkContent() 是与启发式方法并行的手动创作 API:Append() 添加指定类型的子元素,MarkContent() 将页面区域标记为属于该元素。


技巧与最佳实践

  • 在页面内容最终确定后运行一次 Document.AutoTag()——它不会重新标记之后添加的内容,因此请使用 StructTreeRoot.Append() / StructElement.MarkContent() 手动标记之后添加的任何内容。
  • 将 opts.title 传递给 AutoTag()——它还会设置 /ViewerPreferences /DisplayDocTitle,PDF/UA 在文档标题旁也需要该设置。
  • OutlineItem.Dest 可以定位到明确的页面或已命名的目标({ name: '...' })——当目标页码可能因内容更改而移动时,请使用已命名的目标。
  • 当自动分页关闭时检查 AddTOCResult.remainder —— 目录框若对每个条目都太小,会悄悄停止绘制,而不是溢出框体。

常见问题

问题原因修复
Document.GetStructTree() 返回 null文档尚未标记请先调用 Document.AutoTag() 或 Document.CreateStructTree()
大纲项的链接无法解析其 Dest 指向一个在 Document.GetNamedDestinations() 中不存在的目标确认已命名的目标存在,或改用明确的页面 Dest
Page.AddTOC() 在最后一个章节之前停止目标 rect 对每个条目都太小,且 autoPaginate 已关闭扩大 rect,传递 autoPaginate: true,或处理 AddTOCResult.remainder
在 AutoTag() 之后添加的内容在结构树中缺失AutoTag() 只标记运行时出现的内容手动为后续内容加标签,使用 StructTreeRoot.Append() / StructElement.MarkContent()

FAQ

如何向 PDF 添加书签?

构建一个包含 OutlineItem 对象(Title、Dest,可选的 Children)的数组,并将其传递给 Document.SetOutlines()。

目录可以跨越多页吗?

是的 —— 在 Page.AddTOC() 的选项中传入 autoPaginate: true,或者检查 AddTOCResult.remainder 并在新页面上再次调用 AddTOC() 以处理未能容纳的内容。

如何使 PDF 可访问(已标记)?

调用 Document.AutoTag(),它会从页面布局中推断标题、段落、表格和图形。对于之后添加的内容,或需要纠正启发式结果的情况,请直接使用 StructTreeRoot.Append() 和 StructElement.MarkContent()。

页面编号目标和命名目标有什么区别?

页面编号目标(PageDest)指向明确的以 1 为起始的页码。命名目标(NamedDest)指向一个名称,查看器会通过文档自身的命名目标表进行解析——当目标页可能移动时,这非常有用。


API Reference 摘要

类/方法描述
Document.GetOutlines() / Document.SetOutlines()读取或替换文档大纲(书签)树
OutlineItem一个大纲节点:Title,Dest,可选 Children
Page.AddTOC()将目录渲染到页面矩形中
TOCEntry / AddTOCResult目录行的数据以及绘制结果
Document.GetNamedDestinations()列出文档中所有已命名的目标
Document.AutoTag()根据页面布局推断并编写标记结构树
Document.GetStructTree() / Document.CreateStructTree()读取或创建文档的 StructTreeRoot
StructTreeRoot.Append() / StructElement.Append() / StructElement.MarkContent()手动编写或扩展结构树

另请参阅

 中文