How to Work with PDF Structure in TypeScript

How to Work with PDF Structure in TypeScript

This guide shows how to build document navigation and logical structure in PDF files with Aspose.PDF FOSS for TypeScript: a clickable table of contents on a page, a document-level bookmark outline, named destinations, and a tagged structure tree for accessibility. It requires Node.js 22 or later.

Step-by-Step Guide

Step 1: Install the Package

asposefoss/pdf is not yet published — build from source until it ships. See the project README for build instructions.

Verify the installation by importing the Document class in a new TypeScript file — this line should resolve without error once the package is installed:

import { Document } from '@asposefoss/pdf';

Step 2: Import Required Classes

Import Document to open the file and OutlineItem for the bookmark entries built in Step 4:

import { Document, OutlineItem } from '@asposefoss/pdf';

Step 3: Add a Table of Contents

Page.AddTOC() takes an array of { title, page } entries, a bounding rect, and style options; it draws dotted leaders and clickable links to the target pages:

const page = doc.Pages[0];

page.AddTOC(
  [
    { title: '1.  Introduction', page: 2 },
    { title: '2.  Methodology', page: 5 },
    { title: '3.  Results', page: 9 },
  ],
  [72, 160, 400, 600],
  { font: 'Helvetica', fontSize: 13, color: [0.1, 0.1, 0.15], rowGap: 18 },
);

Step 4: Add Bookmarks

Document.SetOutlines() replaces the document’s bookmark panel with an array of OutlineItem objects. Each item can carry a Dest (page destination), styling (Color, Bold), and nested Children for a collapsible tree:

const items: OutlineItem[] = [
  { Title: 'Sales', Dest: { name: 'section.sales' }, Open: true, Color: [0.1, 0.4, 0.1] },
  { Title: 'Appendix', Dest: { name: 'section.appendix' } },
];

doc.SetOutlines(items);

Step 5: Read Outlines and Named Destinations

Document.GetOutlines() returns the current bookmark tree. Document.GetNamedDestinations() returns every named destination registered on the document — useful for verifying that every outline target and TOC link actually resolves to a real page:

const destNames = new Set(doc.GetNamedDestinations().map((d) => d.name));

const walk = (nodes: OutlineItem[]): void => {
  for (const item of nodes) {
    if (item.Dest && 'name' in item.Dest) {
      console.log(destNames.has(item.Dest.name), item.Title);
    }
    if (item.Children) walk(item.Children);
  }
};
walk(doc.GetOutlines());

Step 6: Build a Tagged Structure Tree

Document.GetStructTree() returns the existing structure tree (or null if the document has not been tagged). StructTreeRoot.Append(tag) adds a child element such as 'H2' or 'P' and returns a StructElement, and StructElement.MarkContent(page, quad) associates that element with the page content it describes:

function tagPage(doc: Document, page: Page, headingText: string): void {
  const root = doc.GetStructTree();
  if (!root) throw new Error('tagPage: the document is not tagged yet');

  for (const block of page.GetStructuredText()) {
    const text = block.text.trim();
    if (text.length === 0) continue;
    const el = root.Append(text.startsWith(headingText) ? 'H2' : 'P');
    el.MarkContent(page, block.quad);
  }
}

Common Issues and Fixes

Document.GetStructTree() returns null. The document has not been tagged yet — call Document.CreateStructTree() (or Document.AutoTag()) before hand-authoring elements with StructTreeRoot.Append() / StructElement.MarkContent().

An outline item does not jump to the right page. OutlineItem.Dest must name a destination that actually exists — cross-check item.Dest.name against Document.GetNamedDestinations() before saving, as shown in Step 5.

Page.AddTOC() entries point to the wrong page. page in each TOC entry is the target page’s own page number — confirm it against page.Number on the actual target Page, not an assumed array index.

Bookmarks disappear after a later Document.SetOutlines() call. SetOutlines() replaces the entire bookmark tree — read the current tree with Document.GetOutlines() first, modify the array, then pass the whole array back if bookmarks are being added rather than replaced.

Frequently Asked Questions

Can a bookmark open already expanded?

Yes — set Open: true on the OutlineItem to have that node’s children visible by default in the bookmark panel.

What is the difference between a bookmark and a named destination?

A bookmark (OutlineItem) is a visible entry in the navigation panel; a named destination (Document.GetNamedDestinations() / SetNamedDestination()) is an internal, reusable page target that a bookmark, a TOC entry, or a link annotation can all point to by name.

Does tagging a document affect how it looks?

No — a structure tree (StructTreeRoot, StructElement) is a parallel logical layer describing reading order and semantics; it does not change the page’s visible content.

Can I nest bookmarks?

Yes — set Children on an OutlineItem to an array of further OutlineItem objects to build a collapsible, multi-level tree.

See Also