Skip to content
Lamina PDF
Unreleased

These docs describe changes that aren’t released yet. For the newest release, see the released docs.

Links and outlines

You’ll make four A5 pages joined by links to a website and between pages, an outline (the bookmarks panel) and page numbers that read i, ii, 1, 2. Start from it for a manual, a report or any document longer than a page or two.

The function draws the four pages, links them and returns the PDF’s bytes. It draws text, so in TypeScript it needs the text module, init({ text: true }). Both languages write the same bytes:

packages/sdk/examples/links-and-outlines.ts
// Links and outlines: four A5 pages joined by links, bookmarks and page labels. The title page
// links to a website, to a page and through a named destination; every other page has a linked
// text run back to it. The outline is nested and shown when the file opens.
import { Builder, type BuilderOptions, Font, type LinkTarget, type Surface } from "@laminapdf/sdk";
const TITLES = ["Title page", "Contents", "Chapter 1", "Chapter 2"] as const;
const MARGIN = 36;
const TITLE_BASELINE = 60;
const TITLE_SIZE = 18;
const LINK_SIZE = 12;
/** The baselines of the title page's three links. */
const LINK_BASELINES = [100, 125, 150] as const;
/** A link's clickable box: this wide, this tall, starting this far above the baseline. */
const LINK_WIDTH = 220;
const LINK_HEIGHT = 20;
const LINK_RISE = 15;
/**
* The document's bytes. Needs `init({ text: true })`.
*
* @param options - can fix the file ID, the producer and the creation date.
*/
export function linksAndOutlines(options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
using b = new Builder(options);
const font = Font.standard("Helvetica");
b.font(font);
b.destination("chapter-2", 3);
TITLES.forEach((title, index) => {
const page = b.page("A5");
const s = page.surface();
s.text(title, { font, size: TITLE_SIZE, at: [MARGIN, TITLE_BASELINE] });
if (index === 0) titlePageLinks(s, font);
else {
const link = { target: { type: "page", index: 0 }, contents: "Title page" } as const;
s.text("Back to the title page", { font, size: LINK_SIZE, at: [MARGIN, 100], fill: [0, 0.2, 0.8], link });
}
page.finish();
});
navigation(b);
using doc = b.finish();
return doc.save();
}
/** The title page's links: to a website, to a page, and to a named destination. */
function titlePageLinks(s: Surface, font: Font): void {
const links: readonly (readonly [label: string, target: LinkTarget, contents?: string])[] = [
["Visit example.com", { type: "uri", uri: "https://example.com/" }, "Example"],
["Go to chapter 1", { type: "page", index: 2 }],
["Go to chapter 2 (named)", { type: "named", name: "chapter-2" }],
];
links.forEach(([label, target, contents], i) => {
const baseline = LINK_BASELINES[i] ?? 0;
s.text(label, { font, size: LINK_SIZE, at: [MARGIN, baseline] });
s.link([MARGIN, baseline - LINK_RISE, LINK_WIDTH, LINK_HEIGHT], target, contents);
});
}
/** The outline (bookmarks), the page labels, and the viewer opening with the outline shown. */
function navigation(b: Builder): void {
b.outline([
{ title: "Title page", target: { type: "page", index: 0 } },
{ title: "Contents", target: { type: "page", index: 1 }, color: [0, 0, 0.6] },
{
title: "Chapters",
open: true,
bold: true,
children: [
{ title: "Chapter 1", target: { type: "page", index: 2 } },
{ title: "Chapter 2", target: { type: "named", name: "chapter-2" }, italic: true },
],
},
]);
b.pageLabels([
{ start: 0, style: "lowerRoman" },
{ start: 2, style: "decimal" },
]).viewerPreferences({ pageMode: "outlines" });
}

The result is four A5 pages with an outline and page labels: open the reference PDF.

  • s.link(rect, target, contents) puts a clickable box over each label on the title page. A target is a web address (uri), a page by its index from 0, or a named destination. See Pages and coordinates.
  • destination("chapter-2", 3) names the fourth page, so a link or an outline entry can go there by name. When the builder finishes, a missing page fails with page-index-out-of-range and a missing name with unknown-destination.
  • On the other pages, the link option of text (.link(…) on the run in Rust) makes the blue run itself the link, with no box to work out.
  • contents describes a link in words, for viewers and screen readers. Accessible PDFs need one on every link.
  • outline builds the bookmarks. “Chapters” has no target, so it’s a heading, shown open and in bold with both chapters inside it.
  • pageLabels numbers the pages i and ii, then 1 and 2 from the third page. pageMode: "outlines" asks the viewer to open the file with the outline showing.
  • Where a page link lands: add a fit to the target, such as fit: { type: "width" } (Fit::Width in Rust) to fill the window with the page’s width. destination takes one too.
  • The page numbers: a label also takes a prefix and a first number, so { start: 2, style: "decimal", prefix: "A-" } numbers the chapters A-1 and A-2.
  • The panel on open: pageMode also takes "thumbnails", "attachments" or "none".
  • A title and an author: add metadata({ title, author }), and displayDocTitle: true in the viewer preferences so viewers show the title instead of the file name. See Links, outlines and metadata.

Next: generate, then edit, which opens a PDF you made and changes its metadata.