A PDF document is organized page by page, and reading, printing and archiving all follow that structure. In practice, you often need to adjust the pages of an existing PDF: add a signature page to a contract, append a summary page at the end of a report, or remove a page that no longer belongs. Doing this with desktop software or a server-side re-layout means exporting and uploading files back and forth. Handling it directly in the browser keeps the document on the user's device and shortens the whole path.

Spire.PDF for JavaScript loads, modifies and saves PDF documents directly in the browser based on WebAssembly, managing input and output files through a virtual file system (VFS), with no backend service required.

This article covers three core features:

For installation and project configuration, refer to Integrating Spire.PDF for JavaScript in a React Project. The following examples assume Spire.PDF is installed and the WebAssembly module has been initialized.


Adding a Page to a PDF

Adding a page means inserting one item into the PdfPageCollection. Use Pages.Insert(index) to insert at a specific position; the index is 0-based, and the pages after the insertion point shift back by one. Here the blank page goes into the second position and the existing pages move onward, which suits adding a page in the middle of a document.

function App() {
  const addPageToPdf = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Insert a blank page as the second page (the index is 0-based, so index 1 is the second page)
    doc.Pages.Insert(1);

    // Define the output file name and save the document
    const outputFileName = 'Page_Inserted.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add a Page to PDF</h1>
      <button onClick={addPageToPdf}>
        Start Adding
      </button>
    </div>
  );
}

export default App;

The PDF document after a blank page is inserted as the second page

The PDF document after a blank page is inserted as the second page


Adding a Blank Page at the End of a Document

Spire.PDF for JavaScript also provides the Pages.Add() method, which appends a blank page at the end of a document. It uses A4 as the default page size and 40-point margins on all four sides; when needed, you can set the page size and margins yourself.

function App() {
  const appendPageToPdf = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Append a blank A4 page with zero margins on all four sides
    doc.Pages.Add(pdfModule.PdfPageSize.A4(), new pdfModule.PdfMargins(0.0, 0.0));

    // Define the output file name and save the document
    const outputFileName = 'Page_Appended.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Append a Blank Page to PDF</h1>
      <button onClick={appendPageToPdf}>
        Start Adding
      </button>
    </div>
  );
}

export default App;

The PDF document after a blank A4 page is appended at the end

The PDF document after a blank A4 page is appended at the end


Deleting a Page from a PDF

Deleting a page also works through the page collection: Pages.RemoveAt(index) removes one page by index, starting at 0, and the indexes of the pages after it shift forward by one. Before writing the delete logic, check the current page count with Pages.Count so the index stays in range. Here the second page of the sample document is removed and the remaining pages keep their original order.

function App() {
  const deletePageFromPdf = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Delete the second page (the index is 0-based, so index 1 is the second page)
    doc.Pages.RemoveAt(1);

    // Define the output file name and save the document
    const outputFileName = 'Page_Deleted.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Delete a Page from PDF</h1>
      <button onClick={deletePageFromPdf}>
        Start Deleting
      </button>
    </div>
  );
}

export default App;

The PDF document after the second page is deleted

The PDF document after the second page is deleted


FAQ

How do I set the page size and margins of a new blank page

Reason: Pages.Add and Pages.Insert create blank pages of a regular size with default margins. When a new page has to match a specific paper size or margin, both values need to be passed in.

Solution: The paper size comes from PdfPageSize, such as PdfPageSize.A4() or PdfPageSize.A3(); the margins come from PdfMargins, where the two-argument form new PdfMargins(0.0, 0.0) sets both the vertical and horizontal margins to 0. To control each side separately, pass the named fields:

// A blank A4 page with zero margins on all four sides
doc.Pages.Add(pdfModule.PdfPageSize.A4(), new pdfModule.PdfMargins(0.0, 0.0));

// Set the top, bottom, left and right margins individually
doc.Pages.Add(pdfModule.PdfPageSize.A4(),
  new pdfModule.PdfMargins({ left: 40, top: 40, right: 40, bottom: 40 }));

Why does deleting a page report an index out of range

Reason: RemoveAt(index) is 0-based, and its valid range is 0 to Pages.Count - 1. Without checking the page count first, passing a value equal to or greater than Count goes out of range.

Solution: Check the page count with Pages.Count before deleting and keep the index within range. To delete the last page, for example, the index should be Count - 1:

let total = doc.Pages.Count;

if (total > 0) {
  // Delete the last page
  doc.Pages.RemoveAt(total - 1);
}

What should I watch out for when adding or deleting several pages in a row

Reason: Every insert or delete shifts the indexes of all pages after it. Deleting several pages by fixed indexes from front to back easily removes the wrong ones — once one page is gone, the later indexes recorded earlier have already moved forward.

Solution: Insert, Add and RemoveAt each handle a single page, so repeat the call for batch operations. When deleting several pages, work from back to front so the indexes do not drift:

// Delete from back to front, so the indexes do not shift after each removal
for (let i = doc.Pages.Count - 1; i >= 3; i--) {
  doc.Pages.RemoveAt(i);
}

Get a Free License

If you wish to remove the evaluation message from the result document or remove feature limitations, please contact sales to obtain a temporary license valid for 30 days.

Adding shapes to PDF documents is a common requirement in many business scenarios: marking key areas with lines and boxes, adding a prominent border around content outside a table, distinguishing sections with filled color blocks, or overlaying pie and ellipse shapes on drawings and reports. Doing this by hand in a design tool each time is slow and hard to scale. With the drawing capabilities of Spire.PDF for JavaScript, you can write various shapes directly to PDF pages in the browser and let your code handle the annotation and diagramming work automatically.

Spire.PDF for JavaScript is based on WebAssembly and loads, edits, and saves PDF documents directly in the browser, managing input and output files through a virtual file system (VFS) without any backend service. The core object for drawing shapes on a PDF is the page drawing canvas PdfPage.Canvas: it provides methods such as DrawLine, DrawPie, DrawRectangle, and DrawEllipse.

This article covers four core features:

For installation and project configuration, refer to How to Integrate Spire.PDF for JavaScript in a React Project. The examples below assume Spire.PDF is installed and the WebAssembly module is initialized.


Draw Lines on a PDF Page

When drawing lines you can set the color and thickness, and choose between solid and dashed lines—the dash style is controlled by DashStyle and DashPattern.

function App() {
  const drawLines = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Create a PDF document and add a blank page
    let doc = new pdfModule.PdfDocument();
    let page = doc.Pages.Add();

    // Save the current graphics state
    let state = page.Canvas.Save();

    // Create a red pen for drawing lines
    let pen = new pdfModule.PdfPen({
      pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Red() }),
      width: 2,
    });

    // Starting coordinates and length of the lines
    let x = 30.0;
    let y = 50.0;
    let width = 300.0;

    // Draw a solid line
    page.Canvas.DrawLine({ pen: pen, x1: x, y1: y, x2: x + width, y2: y });

    // Set the dash style and dash pattern
    pen.DashStyle = pdfModule.PdfDashStyle.Dash;
    pen.DashPattern = [3.0, 2.0];

    // Draw a dashed line
    page.Canvas.DrawLine({ pen: pen, x1: x, y1: y + 60.0, x2: x + width, y2: y + 60.0 });

    // Restore the graphics state
    page.Canvas.Restore({ state: state });

    // Define the output file name and save the document
    const outputFileName = 'DrawLines_result.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Draw Lines in PDF</h1>
      <button onClick={drawLines}>
        Draw
      </button>
    </div>
  );
}

export default App;

The result of drawing one solid line and one dashed line on a PDF page

The result of drawing one solid line and one dashed line on a PDF page


Draw a Pie on a PDF Page

Pies express proportions: the bounding rectangle sets the position and size, while startAngle and sweepAngle set the opening angle of the sector.

function App() {
  const drawPie = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Create a PDF document and add a blank page
    let doc = new pdfModule.PdfDocument();
    let page = doc.Pages.Add();

    // Save the current graphics state
    let state = page.Canvas.Save();

    // Create a dark red pen
    let pen = new pdfModule.PdfPen({
      pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_DarkRed() }),
      width: 2,
    });

    // Draw the first pie
    page.Canvas.DrawPie({ pen: pen, x: 10.0, y: 30.0, width: 130.0, height: 130.0, startAngle: 360.0, sweepAngle: 300.0 });

    // Draw the second pie
    page.Canvas.DrawPie({ pen: pen, x: 160.0, y: 30.0, width: 130.0, height: 130.0, startAngle: 360.0, sweepAngle: 330.0 });

    // Draw the third pie
    page.Canvas.DrawPie({ pen: pen, x: 320.0, y: 30.0, width: 130.0, height: 130.0, startAngle: 360.0, sweepAngle: 360.0 });

    // Restore the graphics state
    page.Canvas.Restore({ state: state });

    // Define the output file name and save the document
    const outputFileName = 'DrawPie_result.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Draw a Pie in PDF</h1>
      <button onClick={drawPie}>
        Draw
      </button>
    </div>
  );
}

export default App;

The result of drawing three pies on a PDF page

The result of drawing three pies on a PDF page


Draw a Rectangle on a PDF Page

A rectangle can be drawn as an outline only, or filled. Besides a solid color (PdfSolidBrush), the fill also supports a linear gradient (PdfLinearGradientBrush) and a radial gradient (PdfRadialGradientBrush).

function App() {
  const drawRectangle = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Create a PDF document and add a blank page
    let doc = new pdfModule.PdfDocument();
    let page = doc.Pages.Add();

    // Save the current graphics state
    let state = page.Canvas.Save();

    // Create a black pen
    let pen = new pdfModule.PdfPen({
      pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Black() }),
      width: 1,
    });

    // Draw a rectangle outline with the pen
    page.Canvas.DrawRectangle({
      pen: pen,
      rectangle: new pdfModule.RectangleF({
        location: new pdfModule.PointF(20.0, 30.0),
        size: new pdfModule.SizeF({ width: 150.0, height: 120.0 }),
      }),
    });

    // Create a linear gradient brush
    let linearGradientBrush = new pdfModule.PdfLinearGradientBrush({
      point1: new pdfModule.PointF(200.0, 30.0),
      point2: new pdfModule.PointF(350.0, 150.0),
      color1: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Green() }),
      color2: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Red() }),
    });

    // Draw a filled rectangle with the linear gradient brush
    page.Canvas.DrawRectangle({
      brush: linearGradientBrush,
      rectangle: new pdfModule.RectangleF({
        location: new pdfModule.PointF(200.0, 30.0),
        size: new pdfModule.SizeF({ width: 150.0, height: 120.0 }),
      }),
    });

    // Create a radial gradient brush
    let radialGradientBrush = new pdfModule.PdfRadialGradientBrush({
      centreStart: new pdfModule.PointF(380.0, 30.0),
      radiusStart: 150.0,
      centreEnd: new pdfModule.PointF(530.0, 150.0),
      radiusEnd: 150.0,
      colorStart: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Orange() }),
      colorEnd: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Blue() }),
    });

    // Draw a filled rectangle with the radial gradient brush
    page.Canvas.DrawRectangle({
      brush: radialGradientBrush,
      rectangle: new pdfModule.RectangleF({
        location: new pdfModule.PointF(380.0, 30.0),
        size: new pdfModule.SizeF({ width: 150.0, height: 120.0 }),
      }),
    });

    // Restore the graphics state
    page.Canvas.Restore({ state: state });

    // Define the output file name and save the document
    const outputFileName = 'DrawRectangle_result.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Draw a Rectangle in PDF</h1>
      <button onClick={drawRectangle}>
        Draw
      </button>
    </div>
  );
}

export default App;

The result of drawing a rectangle outline and gradient-filled rectangles on a PDF page

The result of drawing a rectangle outline and gradient-filled rectangles on a PDF page


Draw an Ellipse on a PDF Page

An ellipse likewise supports outlines and fills: use PdfPen for the outline and PdfSolidBrush for the fill, or take one of the preset pens from PdfPens.

function App() {
  const drawEllipse = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Create a PDF document and add a blank page
    let doc = new pdfModule.PdfDocument();
    let page = doc.Pages.Add();

    // Save the current graphics state
    let state = page.Canvas.Save();

    // Create a CadetBlue pen
    let pen = pdfModule.PdfPens.get_CadetBlue();

    // Draw the ellipse outline
    page.Canvas.DrawEllipse({ pen: pen, x: 50.0, y: 30.0, width: 120.0, height: 100.0 });

    // Create a fill brush
    let brush = new pdfModule.PdfSolidBrush({
      pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_CadetBlue() }),
    });

    // Draw the filled ellipse
    page.Canvas.DrawEllipse({ brush: brush, x: 180.0, y: 30.0, width: 120.0, height: 100.0 });

    // Restore the graphics state
    page.Canvas.Restore({ state: state });

    // Define the output file name and save the document
    const outputFileName = 'DrawEllipse_result.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Draw an Ellipse in PDF</h1>
      <button onClick={drawEllipse}>
        Draw
      </button>
    </div>
  );
}

export default App;

The result of drawing an ellipse outline and a filled ellipse on a PDF page

The result of drawing an ellipse outline and a filled ellipse on a PDF page


FAQ

Why does the drawn shape appear at the edge of the page or outside the visible area

Cause: The coordinates used by DrawLine, DrawPie, DrawRectangle, and DrawEllipse have their origin at the bottom-left corner of the page, with the x-axis pointing right and the y-axis pointing up, in units of points. If you copy screen coordinates directly (where the origin is at the top-left), the drawn shape will end up in the opposite position or outside the page.

Solution: Convert the coordinates using the bottom-left corner of the page as the origin. You can read the page size first and then lay out the shape, for example by using PdfPage.Size to get the page width and height and calculating the shape's position from them:

// Get the page size and calculate coordinates with the bottom-left corner as the origin
let size = page.Size;
let x = size.Width / 4;
let y = size.Height / 3;
page.Canvas.DrawRectangle({ pen: pen, x: x, y: y, width: 200, height: 120 });

Why does the existing content on the page shift after drawing

Cause: Drawing modifies the canvas's current transform and graphics state. If you change the coordinate system with ScaleTransform, TranslateTransform, and similar methods before drawing, or fail to restore the state afterward, subsequent content will be affected.

Solution: Use Canvas.Save and Canvas.Restore in pairs, wrapping the drawing operations between them, to make sure the canvas state is restored to its previous level once drawing is complete:

// Save the state before drawing
let state = page.Canvas.Save();
// ... perform drawing ...
// Restore the state after drawing
page.Canvas.Restore({ state: state });

Why can't I see the drawn shape in the saved PDF

Cause: Shapes are drawn onto the canvas object. If you do not call doc.SaveToFile to write the document back to a file after drawing, or if the output file and the file read for download are not the same name, you will still see the original content.

Solution: Make sure you call doc.SaveToFile(outputFileName) to save after drawing, and read and download from the virtual file system using the same outputFileName:

// Save the document to the specified file name
doc.SaveToFile(outputFileName);
doc.Close();

// Read from the VFS using the same file name to trigger a download
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);

Get a Free License

If you want to remove the evaluation message from the result documents or get rid of the feature limitations, please contact our sales team to obtain a temporary license valid for 30 days.

A bookmark in a PDF records the document's outline structure, and a bookmark can hold child bookmarks of its own, nesting level by level into a tree. Reading that information has plenty of practical uses — exporting it as a table of contents, generating site navigation from bookmark titles, or locating a specific page for further processing. All of it calls for a program that can walk the whole bookmark tree and pull out the content of each node.

Spire.PDF for JavaScript processes PDF documents directly in the browser based on WebAssembly, managing input and output files through a virtual file system (VFS), with no backend service required. The core entry point for extracting bookmarks is the PdfDocument.Bookmarks property, which returns a PdfBookmarkCollection; each PdfBookmark object in it exposes its own child bookmark collection, so together they form the complete bookmark tree. Every bookmark node provides properties such as Title and DisplayStyle for reading its appearance, and Destination.Page combined with PdfPageCollection.IndexOf gives you the page number the bookmark points to.

This article covers two core features:

For installation and project configuration, refer to Integrating Spire.PDF for JavaScript in a React Project. The following examples assume Spire.PDF is installed and the WebAssembly module has been initialized.


Extracting All PDF Bookmarks

PdfDocument.Bookmarks returns only the top-level bookmark collection, while bookmarks themselves can contain child bookmarks. To read out every bookmark in the document you need a recursive function that walks the PdfBookmarkCollection level by level, reads the Title (bookmark title) and DisplayStyle (text style) of each node, and records them with indentation by level, producing a complete outline list in the end.

function App() {
  const extractAllBookmarks = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be processed into the VFS
    const inputFileName = 'Sample.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // String that holds the extracted result
    let content = 'All bookmarks in the PDF document:\r\n';

    // Recursively traverse the bookmark collection, recording each title and text style with indentation by level
    const collectBookmarks = (bookmarks, indent) => {
      for (let i = 0; i < bookmarks.Count; i++) {
        let bookmark = bookmarks.get_Item(i);

        // Record the title and text style of the current bookmark
        content += indent + bookmark.Title + ' (' + bookmark.DisplayStyle.toString() + ')\r\n';

        // If there are child bookmarks, process them recursively with more indentation
        if (bookmark.Count > 0) {
          collectBookmarks(bookmark, indent + '    ');
        }
      }
    };

    // Start extracting from the top-level bookmarks
    collectBookmarks(doc.Bookmarks, '');

    // Write the extracted result to a file and trigger the download
    const outputFileName = 'AllBookmarks.txt';
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, content);
    doc.Close();

    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Extract All PDF Bookmarks</h1>
      <button onClick={extractAllBookmarks}>
        Start Extracting
      </button>
    </div>
  );
}

export default App;

The list of all bookmark titles and text styles extracted recursively

The list of all bookmark titles and text styles extracted recursively


Getting the Page Number of a Bookmark

Besides its title, a bookmark also carries a jump destination. Through PdfBookmark.Destination.Page you can obtain the PdfPage object the bookmark points to, and then use PdfPageCollection.IndexOf to get its index within the document. Because the index starts from 0, adding 1 gives the page number shown in a reader. This is commonly used to export a bookmark list as a "title — page number" table of contents.

function App() {
  const getBookmarkPageNumber = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be processed into the VFS
    const inputFileName = 'Sample.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Read the page number each top-level bookmark points to, one by one
    let content = 'Bookmarks and their page numbers:\r\n';
    for (let i = 0; i < doc.Bookmarks.Count; i++) {
      let bookmark = doc.Bookmarks.get_Item(i);

      // Destination.Page gives the page the bookmark points to; IndexOf returns its 0-based index
      let pageNumber = doc.Pages.IndexOf(bookmark.Destination.Page) + 1;

      content += bookmark.Title + ' — Page ' + pageNumber + '\r\n';
    }

    // Write the extracted result to a file and trigger the download
    const outputFileName = 'BookmarkPageNumber.txt';
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, content);
    doc.Close();

    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get the Page Number of a Bookmark</h1>
      <button onClick={getBookmarkPageNumber}>
        Start Extracting
      </button>
    </div>
  );
}

export default App;

The title of each bookmark and the page number it points to

The title of each bookmark and the page number it points to


FAQ

Why is the number of extracted bookmarks smaller than the outline shown in a reader

Reason: doc.Bookmarks returns only the top-level bookmark collection, and its Count counts only the nodes at that level. Child bookmarks nested under a chapter are reached through the node's own collection and are otherwise not counted.

Solution: Traverse the whole bookmark tree recursively and add up the nodes at every level:

function countBookmarks(bookmarks) {
  let total = 0;
  for (let i = 0; i < bookmarks.Count; i++) {
    total += 1;
    // Recursively add the child bookmarks
    total += countBookmarks(bookmarks.get_Item(i));
  }
  return total;
}

const total = countBookmarks(doc.Bookmarks);

Why is the extracted DisplayStyle always Regular

Reason: PdfBookmark.DisplayStyle returns the text style a bookmark is displayed with in the outline panel. Only when the bookmark itself is explicitly set to a style such as Bold or Italic will the value read back differ from the default Regular. It reflects the bookmark's appearance setting, not the font used by the bookmark title in the page content.

Solution: Record the enum value as it is; if you only need to tell whether it is bold or italic, compare it against the PdfTextStyle values one by one:

let style = 'Regular';
if (bookmark.DisplayStyle === pdfModule.PdfTextStyle.Bold) {
  style = 'Bold';
} else if (bookmark.DisplayStyle === pdfModule.PdfTextStyle.Italic) {
  style = 'Italic';
}

Why does the page number from Destination.Page differ from the one shown in a reader

Reason: PdfPageCollection.IndexOf returns the page's index within the collection, counting from 0, while a reader shows page numbers from 1, so using the index directly is off by one.

Solution: Add 1 to the index to match what the reader shows:

// The index is 0-based, so add 1 to get the page number shown in a reader
let pageNumber = doc.Pages.IndexOf(bookmark.Destination.Page) + 1;

In addition, if a bookmark points to a page that no longer exists (for example, the target page was deleted), Destination may be empty, so check for null before reading it:

if (bookmark.Destination && bookmark.Destination.Page) {
  let pageNumber = doc.Pages.IndexOf(bookmark.Destination.Page) + 1;
}

Get a Free License

If you wish to remove the evaluation message from the result document or remove feature limitations, please contact sales to obtain a temporary license valid for 30 days.

page 23